refactor(lua): rename vim.diff => vim.text.diff #34864

Problem:
`vim.diff()` was introduced before we had the `vim.text` module, where
it obviously belongs.

Solution:
Move it.
This commit is contained in:
Justin M. Keyes
2025-07-12 18:36:07 -04:00
committed by GitHub
parent 430be9d01d
commit f3a54e7ccf
12 changed files with 177 additions and 174 deletions

View File

@ -38,7 +38,7 @@ LSP
LUA LUA
todo *vim.diff()* Renamed to |vim.text.diff()|
VIMSCRIPT VIMSCRIPT

View File

@ -581,7 +581,7 @@ A subset of the `vim.*` stdlib is available in threads, including:
- `vim.mpack` and `vim.json` (useful for serializing messages between threads) - `vim.mpack` and `vim.json` (useful for serializing messages between threads)
- `require` in threads can use Lua packages from the global |package.path| - `require` in threads can use Lua packages from the global |package.path|
- `print()` and `vim.inspect` - `print()` and `vim.inspect`
- `vim.diff` - `vim.text.diff`
- Most utility functions in `vim.*` that work with pure Lua values, like - Most utility functions in `vim.*` that work with pure Lua values, like
`vim.split`, `vim.tbl_*`, `vim.list_*`, etc. `vim.split`, `vim.tbl_*`, `vim.list_*`, etc.
- `vim.is_thread()` returns true from a non-main thread. - `vim.is_thread()` returns true from a non-main thread.
@ -647,74 +647,6 @@ vim.hl.range({bufnr}, {ns}, {higroup}, {start}, {finish}, {opts})
manually. nil is returned if timeout is not specified manually. nil is returned if timeout is not specified
==============================================================================
VIM.DIFF *vim.diff*
vim.diff({a}, {b}, {opts}) *vim.diff()*
Run diff on strings {a} and {b}. Any indices returned by this function,
either directly or via callback arguments, are 1-based.
Examples: >lua
vim.diff('a\n', 'b\nc\n')
-- =>
-- @@ -1 +1,2 @@
-- -a
-- +b
-- +c
vim.diff('a\n', 'b\nc\n', {result_type = 'indices'})
-- =>
-- {
-- {1, 1, 1, 2}
-- }
<
Parameters: ~
• {a} (`string`) First string to compare
• {b} (`string`) Second string to compare
• {opts} (`table?`) Optional parameters:
• {on_hunk}?
(`fun(start_a: integer, count_a: integer, start_b: integer, count_b: integer): integer?`)
Invoked for each hunk in the diff. Return a negative number
to cancel the callback for any remaining hunks. Arguments:
• `start_a` (`integer`): Start line of hunk in {a}.
• `count_a` (`integer`): Hunk size in {a}.
• `start_b` (`integer`): Start line of hunk in {b}.
• `count_b` (`integer`): Hunk size in {b}.
• {result_type}? (`'unified'|'indices'`, default: `'unified'`)
Form of the returned diff:
• `unified`: String in unified format.
• `indices`: Array of hunk locations. Note: This option is
ignored if `on_hunk` is used.
• {linematch}? (`boolean|integer`) Run linematch on the
resulting hunks from xdiff. When integer, only hunks upto
this size in lines are run through linematch. Requires
`result_type = indices`, ignored otherwise.
• {algorithm}? (`'myers'|'minimal'|'patience'|'histogram'`,
default: `'myers'`) Diff algorithm to use. Values:
• `myers`: the default algorithm
• `minimal`: spend extra time to generate the smallest
possible diff
• `patience`: patience diff algorithm
• `histogram`: histogram diff algorithm
• {ctxlen}? (`integer`) Context length
• {interhunkctxlen}? (`integer`) Inter hunk context length
• {ignore_whitespace}? (`boolean`) Ignore whitespace
• {ignore_whitespace_change}? (`boolean`) Ignore whitespace
change
• {ignore_whitespace_change_at_eol}? (`boolean`) Ignore
whitespace change at end-of-line.
• {ignore_cr_at_eol}? (`boolean`) Ignore carriage return at
end-of-line
• {ignore_blank_lines}? (`boolean`) Ignore blank lines
• {indent_heuristic}? (`boolean`) Use the indent heuristic for
the internal diff library.
Return: ~
(`string|integer[][]?`) See {opts.result_type}. `nil` if
{opts.on_hunk} is given.
============================================================================== ==============================================================================
VIM.MPACK *vim.mpack* VIM.MPACK *vim.mpack*
@ -4959,6 +4891,70 @@ vim.snippet.stop() *vim.snippet.stop()*
============================================================================== ==============================================================================
Lua module: vim.text *vim.text* Lua module: vim.text *vim.text*
vim.text.diff({a}, {b}, {opts}) *vim.text.diff()*
Run diff on strings {a} and {b}. Any indices returned by this function,
either directly or via callback arguments, are 1-based.
Examples: >lua
vim.text.diff('a\n', 'b\nc\n')
-- =>
-- @@ -1 +1,2 @@
-- -a
-- +b
-- +c
vim.text.diff('a\n', 'b\nc\n', {result_type = 'indices'})
-- =>
-- {
-- {1, 1, 1, 2}
-- }
<
Parameters: ~
• {a} (`string`) First string to compare
• {b} (`string`) Second string to compare
• {opts} (`table?`) Optional parameters:
• {on_hunk}?
(`fun(start_a: integer, count_a: integer, start_b: integer, count_b: integer): integer?`)
Invoked for each hunk in the diff. Return a negative number
to cancel the callback for any remaining hunks. Arguments:
• `start_a` (`integer`): Start line of hunk in {a}.
• `count_a` (`integer`): Hunk size in {a}.
• `start_b` (`integer`): Start line of hunk in {b}.
• `count_b` (`integer`): Hunk size in {b}.
• {result_type}? (`'unified'|'indices'`, default: `'unified'`)
Form of the returned diff:
• `unified`: String in unified format.
• `indices`: Array of hunk locations. Note: This option is
ignored if `on_hunk` is used.
• {linematch}? (`boolean|integer`) Run linematch on the
resulting hunks from xdiff. When integer, only hunks upto
this size in lines are run through linematch. Requires
`result_type = indices`, ignored otherwise.
• {algorithm}? (`'myers'|'minimal'|'patience'|'histogram'`,
default: `'myers'`) Diff algorithm to use. Values:
• `myers`: the default algorithm
• `minimal`: spend extra time to generate the smallest
possible diff
• `patience`: patience diff algorithm
• `histogram`: histogram diff algorithm
• {ctxlen}? (`integer`) Context length
• {interhunkctxlen}? (`integer`) Inter hunk context length
• {ignore_whitespace}? (`boolean`) Ignore whitespace
• {ignore_whitespace_change}? (`boolean`) Ignore whitespace
change
• {ignore_whitespace_change_at_eol}? (`boolean`) Ignore
whitespace change at end-of-line.
• {ignore_cr_at_eol}? (`boolean`) Ignore carriage return at
end-of-line
• {ignore_blank_lines}? (`boolean`) Ignore blank lines
• {indent_heuristic}? (`boolean`) Use the indent heuristic for
the internal diff library.
Return: ~
(`string|integer[][]?`) See {opts.result_type}. `nil` if
{opts.on_hunk} is given.
vim.text.hexdecode({enc}) *vim.text.hexdecode()* vim.text.hexdecode({enc}) *vim.text.hexdecode()*
Hex decode a string. Hex decode a string.

View File

@ -140,7 +140,7 @@ The following new APIs or features were added.
• 'diffopt' now includes a `linematch` option to enable a second-stage diff on • 'diffopt' now includes a `linematch` option to enable a second-stage diff on
individual hunks to provide much more accurate diffs. This option is also individual hunks to provide much more accurate diffs. This option is also
available to |vim.diff()| available to |vim.text.diff()|
See https://github.com/neovim/neovim/pull/14537. See https://github.com/neovim/neovim/pull/14537.

View File

@ -87,7 +87,7 @@ LSP
LUA LUA
todo Renamed `vim.diff` to `vim.text.diff`.
OPTIONS OPTIONS

View File

@ -1314,9 +1314,12 @@ end
require('vim._options') require('vim._options')
-- Remove at Nvim 1.0 --- Remove at Nvim 1.0
---@deprecated ---@deprecated
vim.loop = vim.uv vim.loop = vim.uv
--- Renamed to `vim.text.diff`, remove at Nvim 1.0
---@deprecated
vim.diff = vim._diff ---@type fun(a: string, b: string, opts?: vim.text.diff.Opts): string|integer[][]?
-- Deprecated. Remove at Nvim 2.0 -- Deprecated. Remove at Nvim 2.0
vim.highlight = vim._defer_deprecated_module('vim.highlight', 'vim.hl') vim.highlight = vim._defer_deprecated_module('vim.highlight', 'vim.hl')

View File

@ -1,71 +0,0 @@
---@meta
--- Optional parameters:
--- @class vim.diff.Opts
--- @inlinedoc
---
--- Invoked for each hunk in the diff. Return a negative number
--- to cancel the callback for any remaining hunks.
--- Arguments:
--- - `start_a` (`integer`): Start line of hunk in {a}.
--- - `count_a` (`integer`): Hunk size in {a}.
--- - `start_b` (`integer`): Start line of hunk in {b}.
--- - `count_b` (`integer`): Hunk size in {b}.
--- @field on_hunk? fun(start_a: integer, count_a: integer, start_b: integer, count_b: integer): integer?
---
--- Form of the returned diff:
--- - `unified`: String in unified format.
--- - `indices`: Array of hunk locations.
--- Note: This option is ignored if `on_hunk` is used.
--- (default: `'unified'`)
--- @field result_type? 'unified'|'indices'
---
--- Run linematch on the resulting hunks from xdiff. When integer, only hunks
--- upto this size in lines are run through linematch.
--- Requires `result_type = indices`, ignored otherwise.
--- @field linematch? boolean|integer
---
--- Diff algorithm to use. Values:
--- - `myers`: the default algorithm
--- - `minimal`: spend extra time to generate the smallest possible diff
--- - `patience`: patience diff algorithm
--- - `histogram`: histogram diff algorithm
--- (default: `'myers'`)
--- @field algorithm? 'myers'|'minimal'|'patience'|'histogram'
--- @field ctxlen? integer Context length
--- @field interhunkctxlen? integer Inter hunk context length
--- @field ignore_whitespace? boolean Ignore whitespace
--- @field ignore_whitespace_change? boolean Ignore whitespace change
--- @field ignore_whitespace_change_at_eol? boolean Ignore whitespace change at end-of-line.
--- @field ignore_cr_at_eol? boolean Ignore carriage return at end-of-line
--- @field ignore_blank_lines? boolean Ignore blank lines
--- @field indent_heuristic? boolean Use the indent heuristic for the internal diff library.
-- luacheck: no unused args
--- Run diff on strings {a} and {b}. Any indices returned by this function,
--- either directly or via callback arguments, are 1-based.
---
--- Examples:
---
--- ```lua
--- vim.diff('a\n', 'b\nc\n')
--- -- =>
--- -- @@ -1 +1,2 @@
--- -- -a
--- -- +b
--- -- +c
---
--- vim.diff('a\n', 'b\nc\n', {result_type = 'indices'})
--- -- =>
--- -- {
--- -- {1, 1, 1, 2}
--- -- }
--- ```
---
---@param a string First string to compare
---@param b string Second string to compare
---@param opts? vim.diff.Opts
---@return string|integer[][]?
--- See {opts.result_type}. `nil` if {opts.on_hunk} is given.
function vim.diff(a, b, opts) end

View File

@ -2,6 +2,80 @@
local M = {} local M = {}
--- Optional parameters:
--- @class vim.text.diff.Opts
--- @inlinedoc
---
--- Invoked for each hunk in the diff. Return a negative number
--- to cancel the callback for any remaining hunks.
--- Arguments:
--- - `start_a` (`integer`): Start line of hunk in {a}.
--- - `count_a` (`integer`): Hunk size in {a}.
--- - `start_b` (`integer`): Start line of hunk in {b}.
--- - `count_b` (`integer`): Hunk size in {b}.
--- @field on_hunk? fun(start_a: integer, count_a: integer, start_b: integer, count_b: integer): integer?
---
--- Form of the returned diff:
--- - `unified`: String in unified format.
--- - `indices`: Array of hunk locations.
--- Note: This option is ignored if `on_hunk` is used.
--- (default: `'unified'`)
--- @field result_type? 'unified'|'indices'
---
--- Run linematch on the resulting hunks from xdiff. When integer, only hunks
--- upto this size in lines are run through linematch.
--- Requires `result_type = indices`, ignored otherwise.
--- @field linematch? boolean|integer
---
--- Diff algorithm to use. Values:
--- - `myers`: the default algorithm
--- - `minimal`: spend extra time to generate the smallest possible diff
--- - `patience`: patience diff algorithm
--- - `histogram`: histogram diff algorithm
--- (default: `'myers'`)
--- @field algorithm? 'myers'|'minimal'|'patience'|'histogram'
--- @field ctxlen? integer Context length
--- @field interhunkctxlen? integer Inter hunk context length
--- @field ignore_whitespace? boolean Ignore whitespace
--- @field ignore_whitespace_change? boolean Ignore whitespace change
--- @field ignore_whitespace_change_at_eol? boolean Ignore whitespace change at end-of-line.
--- @field ignore_cr_at_eol? boolean Ignore carriage return at end-of-line
--- @field ignore_blank_lines? boolean Ignore blank lines
--- @field indent_heuristic? boolean Use the indent heuristic for the internal diff library.
-- luacheck: no unused args
--- Run diff on strings {a} and {b}. Any indices returned by this function,
--- either directly or via callback arguments, are 1-based.
---
--- Examples:
---
--- ```lua
--- vim.text.diff('a\n', 'b\nc\n')
--- -- =>
--- -- @@ -1 +1,2 @@
--- -- -a
--- -- +b
--- -- +c
---
--- vim.text.diff('a\n', 'b\nc\n', {result_type = 'indices'})
--- -- =>
--- -- {
--- -- {1, 1, 1, 2}
--- -- }
--- ```
---
---@diagnostic disable-next-line: undefined-doc-param
---@param a string First string to compare
---@diagnostic disable-next-line: undefined-doc-param
---@param b string Second string to compare
---@diagnostic disable-next-line: undefined-doc-param
---@param opts? vim.text.diff.Opts
---@return string|integer[][]? # See {opts.result_type}. `nil` if {opts.on_hunk} is given.
function M.diff(...)
return vim._diff(...)
end
local alphabet = '0123456789ABCDEF' local alphabet = '0123456789ABCDEF'
local atoi = {} ---@type table<string, integer> local atoi = {} ---@type table<string, integer>
local itoa = {} ---@type table<integer, string> local itoa = {} ---@type table<integer, string>

View File

@ -133,7 +133,6 @@ local config = {
filename = 'lua.txt', filename = 'lua.txt',
section_order = { section_order = {
'hl.lua', 'hl.lua',
'diff.lua',
'mpack.lua', 'mpack.lua',
'json.lua', 'json.lua',
'base64.lua', 'base64.lua',
@ -185,7 +184,6 @@ local config = {
'runtime/lua/vim/text.lua', 'runtime/lua/vim/text.lua',
'runtime/lua/vim/glob.lua', 'runtime/lua/vim/glob.lua',
'runtime/lua/vim/_meta/builtin.lua', 'runtime/lua/vim/_meta/builtin.lua',
'runtime/lua/vim/_meta/diff.lua',
'runtime/lua/vim/_meta/mpack.lua', 'runtime/lua/vim/_meta/mpack.lua',
'runtime/lua/vim/_meta/json.lua', 'runtime/lua/vim/_meta/json.lua',
'runtime/lua/vim/_meta/base64.lua', 'runtime/lua/vim/_meta/base64.lua',
@ -230,7 +228,6 @@ local config = {
'mpack', 'mpack',
'json', 'json',
'base64', 'base64',
'diff',
'spell', 'spell',
'regex', 'regex',
'lpeg', 'lpeg',

View File

@ -761,9 +761,9 @@ void nlua_state_add_stdlib(lua_State *const lstate, bool is_thread)
lua_setfield(lstate, -2, "lpeg"); lua_setfield(lstate, -2, "lpeg");
lua_pop(lstate, 4); lua_pop(lstate, 4);
// vim.diff // vim.text.diff
lua_pushcfunction(lstate, &nlua_xdl_diff); lua_pushcfunction(lstate, &nlua_xdl_diff);
lua_setfield(lstate, -2, "diff"); lua_setfield(lstate, -2, "_diff");
// vim.json // vim.json
lua_cjson_new(lstate); lua_cjson_new(lstate);

View File

@ -164,9 +164,7 @@ static int call_on_hunk_cb(int start_a, int count_a, int start_b, int count_b, v
lua_pushinteger(lstate, count_b); lua_pushinteger(lstate, count_b);
if (lua_pcall(lstate, 4, 1, 0) != 0) { if (lua_pcall(lstate, 4, 1, 0) != 0) {
api_set_error(err, kErrorTypeException, api_set_error(err, kErrorTypeException, "on_hunk: %s", lua_tostring(lstate, -1));
"error running function on_hunk: %s",
lua_tostring(lstate, -1));
return -1; return -1;
} }

View File

@ -217,7 +217,7 @@ describe('thread', function()
it('diff', function() it('diff', function()
exec_lua [[ exec_lua [[
local entry = function(async) local entry = function(async)
async:send(vim.diff('Hello\n', 'Helli\n')) async:send(vim.text.diff('Hello\n', 'Helli\n'))
end end
local on_async = function(ret) local on_async = function(ret)
vim.rpcnotify(1, 'result', ret) vim.rpcnotify(1, 'result', ret)
@ -372,7 +372,7 @@ describe('threadpool', function()
it('work', function() it('work', function()
exec_lua [[ exec_lua [[
local work_fn = function() local work_fn = function()
return vim.diff('Hello\n', 'Helli\n') return vim.text.diff('Hello\n', 'Helli\n')
end end
local after_work_fn = function(ret) local after_work_fn = function(ret)
vim.rpcnotify(1, 'result', ret) vim.rpcnotify(1, 'result', ret)

View File

@ -27,7 +27,7 @@ describe('xdiff bindings', function()
'', '',
}, '\n'), }, '\n'),
exec_lua(function() exec_lua(function()
return vim.diff(a1, b1) return vim.text.diff(a1, b1)
end) end)
) )
@ -43,7 +43,7 @@ describe('xdiff bindings', function()
'', '',
}, '\n'), }, '\n'),
exec_lua(function() exec_lua(function()
return vim.diff(a2, b2) return vim.text.diff(a2, b2)
end) end)
) )
end) end)
@ -53,7 +53,7 @@ describe('xdiff bindings', function()
{ { 1, 1, 1, 1 } }, { { 1, 1, 1, 1 } },
exec_lua(function() exec_lua(function()
local exp = {} --- @type table[] local exp = {} --- @type table[]
assert(vim.diff(a1, b1, { assert(vim.text.diff(a1, b1, {
on_hunk = function(...) on_hunk = function(...)
exp[#exp + 1] = { ... } exp[#exp + 1] = { ... }
end, end,
@ -66,7 +66,7 @@ describe('xdiff bindings', function()
{ { 1, 1, 1, 1 }, { 3, 1, 3, 2 } }, { { 1, 1, 1, 1 }, { 3, 1, 3, 2 } },
exec_lua(function() exec_lua(function()
local exp = {} --- @type table[] local exp = {} --- @type table[]
assert(vim.diff(a2, b2, { assert(vim.text.diff(a2, b2, {
on_hunk = function(...) on_hunk = function(...)
exp[#exp + 1] = { ... } exp[#exp + 1] = { ... }
end, end,
@ -80,7 +80,7 @@ describe('xdiff bindings', function()
{ { 1, 1, 1, 1 }, { 3, 1, 3, 2 } }, { { 1, 1, 1, 1 }, { 3, 1, 3, 2 } },
exec_lua(function() exec_lua(function()
local exp = {} --- @type table[] local exp = {} --- @type table[]
assert(vim.diff(a2, b2, { assert(vim.text.diff(a2, b2, {
on_hunk = function(...) on_hunk = function(...)
exp[#exp + 1] = { ... } exp[#exp + 1] = { ... }
end, end,
@ -92,10 +92,10 @@ describe('xdiff bindings', function()
end) end)
it('with error callback', function() it('with error callback', function()
eq( t.matches(
[[.../xdiff_spec.lua:0: error running function on_hunk: .../xdiff_spec.lua:0: ERROR1]], [[on_hunk: %.%.%./xdiff_spec%.lua%:0%: ERROR1]],
pcall_err(exec_lua, function() pcall_err(exec_lua, function()
vim.diff(a1, b1, { vim.text.diff(a1, b1, {
on_hunk = function() on_hunk = function()
error('ERROR1') error('ERROR1')
end, end,
@ -108,14 +108,14 @@ describe('xdiff bindings', function()
eq( eq(
{ { 1, 1, 1, 1 } }, { { 1, 1, 1, 1 } },
exec_lua(function() exec_lua(function()
return vim.diff(a1, b1, { result_type = 'indices' }) return vim.text.diff(a1, b1, { result_type = 'indices' })
end) end)
) )
eq( eq(
{ { 1, 1, 1, 1 }, { 3, 1, 3, 2 } }, { { 1, 1, 1, 1 }, { 3, 1, 3, 2 } },
exec_lua(function() exec_lua(function()
return vim.diff(a2, b2, { result_type = 'indices' }) return vim.text.diff(a2, b2, { result_type = 'indices' })
end) end)
) )
end) end)
@ -160,7 +160,7 @@ describe('xdiff bindings', function()
'', '',
}, '\n'), }, '\n'),
exec_lua(function() exec_lua(function()
return vim.diff(a, b, { return vim.text.diff(a, b, {
algorithm = 'patience', algorithm = 'patience',
}) })
end) end)
@ -169,20 +169,26 @@ describe('xdiff bindings', function()
end) end)
it('can handle bad args', function() it('can handle bad args', function()
eq([[Expected at least 2 arguments]], pcall_err(exec_lua, [[vim.diff('a')]])) eq([[Expected at least 2 arguments]], pcall_err(exec_lua, [[vim.text.diff('a')]]))
eq([[bad argument #1 to 'diff' (expected string)]], pcall_err(exec_lua, [[vim.diff(1, 2)]])) t.matches(
[[bad argument %#1 to '_?diff' %(expected string%)]],
eq( pcall_err(exec_lua, [[vim.text.diff(1, 2)]])
[[bad argument #3 to 'diff' (expected table)]],
pcall_err(exec_lua, [[vim.diff('a', 'b', true)]])
) )
eq([[invalid key: bad_key]], pcall_err(exec_lua, [[vim.diff('a', 'b', { bad_key = true })]])) t.matches(
[[bad argument %#3 to '_?diff' %(expected table%)]],
pcall_err(exec_lua, [[vim.text.diff('a', 'b', true)]])
)
eq(
[[invalid key: bad_key]],
pcall_err(exec_lua, [[vim.text.diff('a', 'b', { bad_key = true })]])
)
eq( eq(
[[on_hunk is not a function]], [[on_hunk is not a function]],
pcall_err(exec_lua, [[vim.diff('a', 'b', { on_hunk = true })]]) pcall_err(exec_lua, [[vim.text.diff('a', 'b', { on_hunk = true })]])
) )
end) end)
@ -190,7 +196,7 @@ describe('xdiff bindings', function()
eq( eq(
{ { 0, 0, 1, 1 }, { 1, 0, 3, 2 } }, { { 0, 0, 1, 1 }, { 1, 0, 3, 2 } },
exec_lua(function() exec_lua(function()
return vim.diff('\n', '\0\n\n\nb', { linematch = true, result_type = 'indices' }) return vim.text.diff('\n', '\0\n\n\nb', { linematch = true, result_type = 'indices' })
end) end)
) )
end) end)