0
0
mirror of https://github.com/vim/vim.git synced 2025-09-24 03:44:06 -04:00

patch 9.1.0071: Need a diff() Vim script function

Problem:  Need a diff() Vim script function
Solution: Add the diff() Vim script function using the
          xdiff internal diff library, add support for
          "unified" and "indices" mode.
          (Yegappan Lakshmanan)

fixes: #4241
closes: #12321

Signed-off-by: Yegappan Lakshmanan <yegappan@yahoo.com>
Signed-off-by: Christian Brabandt <cb@256bit.org>
This commit is contained in:
Yegappan Lakshmanan
2024-02-01 22:05:27 +01:00
committed by Christian Brabandt
parent 1226b0584a
commit fa37835b8c
13 changed files with 538 additions and 12 deletions

View File

@@ -1,4 +1,4 @@
*builtin.txt* For Vim version 9.1. Last change: 2024 Jan 29
*builtin.txt* For Vim version 9.1. Last change: 2024 Feb 01
VIM REFERENCE MANUAL by Bram Moolenaar
@@ -147,6 +147,8 @@ delete({fname} [, {flags}]) Number delete the file or directory {fname}
deletebufline({buf}, {first} [, {last}])
Number delete lines from buffer {buf}
did_filetype() Number |TRUE| if FileType autocmd event used
diff({fromlist}, {tolist} [, {options}])
List diff two Lists of strings
diff_filler({lnum}) Number diff filler lines about {lnum}
diff_hlID({lnum}, {col}) Number diff highlighting at {lnum}/{col}
digraph_get({chars}) String get the |digraph| of {chars}
@@ -2046,6 +2048,67 @@ did_filetype() Returns |TRUE| when autocommands are being executed and the
editing another buffer to set 'filetype' and load a syntax
file.
diff({fromlist}, {tolist} [, {options}]) *diff()*
Returns a String or a List containing the diff between the
strings in {fromlist} and {tolist}. Uses the Vim internal
diff library to compute the diff.
*E106*
The optional "output" item in {options} specifies the returned
diff format. The following values are supported:
indices Return a List of the starting and ending
indices and a count of the strings in each
diff hunk.
unified Return the unified diff output as a String.
This is the default.
If the "output" item in {options} is "indices", then a List is
returned. Each List item contains a Dict with the following
items for each diff hunk:
from_idx start index in {fromlist} for this diff hunk.
from_count number of strings in {fromlist} that are
added/removed/modified in this diff hunk.
to_idx start index in {tolist} for this diff hunk.
to_count number of strings in {tolist} that are
added/removed/modified in this diff hunk.
The {options} Dict argument also specifies diff options
(similar to 'diffopt') and supports the following items:
iblank ignore changes where lines are all
blank.
icase ignore changes in case of text.
iwhite ignore changes in amount of white
space.
iwhiteall ignore all white space changes.
iwhiteeol ignore white space changes at end of
line.
indent-heuristic use the indent heuristic for the
internal diff library.
algorithm Dict specifying the diff algorithm to
use. Supported boolean items are
"myers", "minimal", "patience" and
"histogram".
For more information about these options, refer to 'diffopt'.
Returns an empty List or String if {fromlist} and {tolist} are
identical.
Examples:
:echo diff(['abc'], ['xxx'])
@@ -1 +1 @@
-abc
+xxx
:echo diff(['abc'], ['xxx'], {'output': 'indices'})
[{'from_idx': 0, 'from_count': 1, 'to_idx': 0, 'to_count': 1}]
:echo diff(readfile('oldfile'), readfile('newfile'))
:echo diff(getbufline(5, 1, '$'), getbufline(6, 1, '$'))
For more examples, refer to |diff-func-examples|
Can also be used as a |method|: >
GetFromList->diff(to_list)
<
diff_filler({lnum}) *diff_filler()*
Returns the number of filler lines above line {lnum}.
These are the lines that were inserted at this point in

View File

@@ -1,4 +1,4 @@
*diff.txt* For Vim version 9.1. Last change: 2023 Apr 04
*diff.txt* For Vim version 9.1. Last change: 2024 Feb 01
VIM REFERENCE MANUAL by Bram Moolenaar
@@ -476,4 +476,43 @@ Otherwise, the expression is evaluated in the context of the script where the
option was set, thus script-local items are available.
DIFF FUNCTION EXAMPLES *diff-func-examples*
Some examples for using the |diff()| function to compute the diff indices
between two Lists of strings are below.
>
" some lines are changed
:echo diff(['abc', 'def', 'ghi'], ['abx', 'rrr', 'xhi'], {'output': 'indices'})
[{'from_idx': 0, 'from_count': 3, 'to_idx': 0, 'to_count': 3}]
" few lines added at the beginning
:echo diff(['ghi'], ['abc', 'def', 'ghi'], {'output': 'indices'})
[{'from_idx': 0, 'from_count': 0, 'to_idx': 0, 'to_count': 2}]
" few lines removed from the beginning
:echo diff(['abc', 'def', 'ghi'], ['ghi'], {'output': 'indices'})
[{'from_idx': 0, 'from_count': 2, 'to_idx': 0, 'to_count': 0}]
" few lines added in the middle
:echo diff(['abc', 'jkl'], ['abc', 'def', 'ghi', 'jkl'], {'output': 'indices'})
[{'from_idx': 1, 'from_count': 0, 'to_idx': 1, 'to_count': 2}]
" few lines removed in the middle
:echo diff(['abc', 'def', 'ghi', 'jkl'], ['abc', 'jkl'], {'output': 'indices'})
[{'from_idx': 1, 'from_count': 2, 'to_idx': 1, 'to_count': 0}]
" few lines added at the end
:echo diff(['abc'], ['abc', 'def', 'ghi'], {'output': 'indices'})
[{'from_idx': 1, 'from_count': 0, 'to_idx': 1, 'to_count': 2}]
" few lines removed from the end
:echo diff(['abc', 'def', 'ghi'], ['abc'], {'output': 'indices'})
[{'from_idx': 1, 'from_count': 2, 'to_idx': 1, 'to_count': 0}]
" disjointed changes
:echo diff(['ab', 'def', 'ghi', 'jkl'], ['abc', 'def', 'ghi', 'jk'], {'output': 'indices'})
[{'from_idx': 0, 'from_count': 1, 'to_idx': 0, 'to_count': 1},
{'from_idx': 3, 'from_count': 1, 'to_idx': 3, 'to_count': 1}]
<
vim:tw=78:ts=8:noet:ft=help:norl:

View File

@@ -4137,6 +4137,7 @@ E1056 vim9.txt /*E1056*
E1057 vim9.txt /*E1057*
E1058 vim9.txt /*E1058*
E1059 vim9.txt /*E1059*
E106 builtin.txt /*E106*
E1060 vim9.txt /*E1060*
E1061 vim9.txt /*E1061*
E1062 eval.txt /*E1062*
@@ -6759,7 +6760,9 @@ dict-identity eval.txt /*dict-identity*
dict-modification eval.txt /*dict-modification*
did_filetype() builtin.txt /*did_filetype()*
diff diff.txt /*diff*
diff() builtin.txt /*diff()*
diff-diffexpr diff.txt /*diff-diffexpr*
diff-func-examples diff.txt /*diff-func-examples*
diff-mode diff.txt /*diff-mode*
diff-options diff.txt /*diff-options*
diff-original-file diff.txt /*diff-original-file*

View File

@@ -1,4 +1,4 @@
*todo.txt* For Vim version 9.1. Last change: 2024 Jan 14
*todo.txt* For Vim version 9.1. Last change: 2024 Feb 01
VIM REFERENCE MANUAL by Bram Moolenaar
@@ -956,9 +956,6 @@ When 'sidescrolloff' is set, using "zl" to go to the end of the line, suddenly
scrolls back. Should allow for this scrolling, like 'scrolloff' does when
using CTRL-E. (Yee Cheng Chin, #3721)
Add function to make use of internal diff, working on two lists and returning
unified diff (list of lines).
When splitting a window with few text lines, the relative cursor position is
kept, which means part of the text isn't displayed. Better show all the text
when possible. (Dylan Lloyd, #3973)

View File

@@ -1,4 +1,4 @@
*usr_41.txt* For Vim version 9.1. Last change: 2024 Jan 13
*usr_41.txt* For Vim version 9.1. Last change: 2024 Feb 01
VIM USER MANUAL - by Bram Moolenaar
@@ -1368,6 +1368,7 @@ Various: *various-functions*
changenr() return number of most recent change
cscope_connection() check if a cscope connection exists
did_filetype() check if a FileType autocommand was used
diff() diff two Lists of strings
eventhandler() check if invoked by an event handler
getpid() get process ID of Vim
getscriptinfo() get list of sourced vim scripts

View File

@@ -42,6 +42,10 @@ static int diff_flags = DIFF_INTERNAL | DIFF_FILLER | DIFF_CLOSE_OFF;
static long diff_algorithm = 0;
#define DIFF_INTERNAL_OUTPUT_UNIFIED 1
#define DIFF_INTERNAL_OUTPUT_INDICES 2
static int diff_internal_output_fmt = DIFF_INTERNAL_OUTPUT_INDICES;
#define LBUFLEN 50 // length of line in diff file
static int diff_a_works = MAYBE; // TRUE when "diff -a" works, FALSE when it
@@ -97,7 +101,8 @@ static void diff_copy_entry(diff_T *dprev, diff_T *dp, int idx_orig, int idx_new
static diff_T *diff_alloc_new(tabpage_T *tp, diff_T *dprev, diff_T *dp);
static int parse_diff_ed(char_u *line, diffhunk_T *hunk);
static int parse_diff_unified(char_u *line, diffhunk_T *hunk);
static int xdiff_out(long start_a, long count_a, long start_b, long count_b, void *priv);
static int xdiff_out_indices(long start_a, long count_a, long start_b, long count_b, void *priv);
static int xdiff_out_unified(void *priv, mmbuffer_t *mb, int nbuf);
#define FOR_ALL_DIFFBLOCKS_IN_TAB(tp, dp) \
for ((dp) = (tp)->tp_first_diff; (dp) != NULL; (dp) = (dp)->df_next)
@@ -1142,7 +1147,10 @@ diff_file_internal(diffio_T *diffio)
emit_cfg.ctxlen = 0; // don't need any diff_context here
emit_cb.priv = &diffio->dio_diff;
emit_cfg.hunk_func = xdiff_out;
if (diff_internal_output_fmt == DIFF_INTERNAL_OUTPUT_INDICES)
emit_cfg.hunk_func = xdiff_out_indices;
else
emit_cb.out_line = xdiff_out_unified;
if (xdl_diff(&diffio->dio_orig.din_mmfile,
&diffio->dio_new.din_mmfile,
&param, &emit_cfg, &emit_cb) < 0)
@@ -3327,10 +3335,10 @@ parse_diff_unified(
/*
* Callback function for the xdl_diff() function.
* Stores the diff output in a grow array.
* Stores the diff output (indices) in a grow array.
*/
static int
xdiff_out(
xdiff_out_indices(
long start_a,
long count_a,
long start_b,
@@ -3357,6 +3365,25 @@ xdiff_out(
return 0;
}
/*
* Callback function for the xdl_diff() function.
* Stores the unified diff output in a grow array.
*/
static int
xdiff_out_unified(
void *priv,
mmbuffer_t *mb,
int nbuf)
{
diffout_T *dout = (diffout_T *)priv;
int i;
for (i = 0; i < nbuf; i++)
ga_concat_len(&dout->dout_ga, (char_u *)mb[i].ptr, mb[i].size);
return 0;
}
#endif // FEAT_DIFF
#if defined(FEAT_EVAL) || defined(PROTO)
@@ -3439,4 +3466,205 @@ f_diff_hlID(typval_T *argvars UNUSED, typval_T *rettv UNUSED)
#endif
}
/*
* Parse the diff options passed in "optarg" to the diff() function and return
* the options in "diffopts" and the diff algorithm in "diffalgo".
*/
static int
parse_diff_optarg(
typval_T *opts,
int *diffopts,
long *diffalgo,
int *diff_output_fmt)
{
dict_T *d = opts->vval.v_dict;
char_u *algo = dict_get_string(d, "algorithm", FALSE);
if (algo != NULL)
{
if (STRNCMP(algo, "myers", 5) == 0)
*diffalgo = 0;
else if (STRNCMP(algo, "minimal", 7) == 0)
*diffalgo = XDF_NEED_MINIMAL;
else if (STRNCMP(algo, "patience", 8) == 0)
*diffalgo = XDF_PATIENCE_DIFF;
else if (STRNCMP(algo, "histogram", 9) == 0)
*diffalgo = XDF_HISTOGRAM_DIFF;
}
char_u *output_fmt = dict_get_string(d, "output", FALSE);
if (output_fmt != NULL)
{
if (STRNCMP(output_fmt, "unified", 7) == 0)
*diff_output_fmt = DIFF_INTERNAL_OUTPUT_UNIFIED;
else if (STRNCMP(output_fmt, "indices", 7) == 0)
*diff_output_fmt = DIFF_INTERNAL_OUTPUT_INDICES;
else
{
semsg(_(e_unsupported_diff_output_format_str), output_fmt);
return FAIL;
}
}
if (dict_get_bool(d, "iblank", FALSE))
*diffopts |= DIFF_IBLANK;
if (dict_get_bool(d, "icase", FALSE))
*diffopts |= DIFF_ICASE;
if (dict_get_bool(d, "iwhite", FALSE))
*diffopts |= DIFF_IWHITE;
if (dict_get_bool(d, "iwhiteall", FALSE))
*diffopts |= DIFF_IWHITEALL;
if (dict_get_bool(d, "iwhiteeol", FALSE))
*diffopts |= DIFF_IWHITEEOL;
if (dict_get_bool(d, "indent-heuristic", FALSE))
*diffalgo |= XDF_INDENT_HEURISTIC;
return OK;
}
/*
* Concatenate the List of strings in "l" and store the result in
* "din->din_mmfile.ptr" and the length in "din->din_mmfile.size".
*/
static void
list_to_diffin(list_T *l, diffin_T *din, int icase)
{
garray_T ga;
listitem_T *li;
char_u *str;
ga_init2(&ga, 512, 4);
FOR_ALL_LIST_ITEMS(l, li)
{
str = tv_get_string(&li->li_tv);
if (icase)
{
str = strlow_save(str);
if (str == NULL)
continue;
}
ga_concat(&ga, str);
ga_concat(&ga, (char_u *)NL_STR);
if (icase)
vim_free(str);
}
if (ga.ga_len > 0)
((char *)ga.ga_data)[ga.ga_len] = NUL;
din->din_mmfile.ptr = (char *)ga.ga_data;
din->din_mmfile.size = ga.ga_len;
}
/*
* Get the start and end indices from the diff "hunk".
*/
static dict_T *
get_diff_hunk_indices(diffhunk_T *hunk)
{
dict_T *hunk_dict;
hunk_dict = dict_alloc();
if (hunk_dict == NULL)
return NULL;
dict_add_number(hunk_dict, "from_idx", hunk->lnum_orig - 1);
dict_add_number(hunk_dict, "from_count", hunk->count_orig);
dict_add_number(hunk_dict, "to_idx", hunk->lnum_new - 1);
dict_add_number(hunk_dict, "to_count", hunk->count_new);
return hunk_dict;
}
/*
* "diff()" function
*/
void
f_diff(typval_T *argvars UNUSED, typval_T *rettv UNUSED)
{
#ifdef FEAT_DIFF
diffio_T dio;
if (check_for_nonnull_list_arg(argvars, 0) == FAIL
|| check_for_nonnull_list_arg(argvars, 1) == FAIL
|| check_for_opt_nonnull_dict_arg(argvars, 2) == FAIL)
return;
CLEAR_FIELD(dio);
dio.dio_internal = TRUE;
ga_init2(&dio.dio_diff.dout_ga, sizeof(char *), 1000);
list_T *orig_list = argvars[0].vval.v_list;
list_T *new_list = argvars[1].vval.v_list;
// Save the 'diffopt' option value and restore it after getting the diff.
int save_diff_flags = diff_flags;
long save_diff_algorithm = diff_algorithm;
long save_diff_output_fmt = diff_internal_output_fmt;
diff_flags = DIFF_INTERNAL;
diff_algorithm = 0;
diff_internal_output_fmt = DIFF_INTERNAL_OUTPUT_UNIFIED;
if (argvars[2].v_type != VAR_UNKNOWN)
if (parse_diff_optarg(&argvars[2], &diff_flags, &diff_algorithm,
&diff_internal_output_fmt) == FAIL)
{
diff_internal_output_fmt = save_diff_output_fmt;
return;
}
// Concatenate the List of strings into a single string using newline
// separator. Internal diff library expects a single string.
list_to_diffin(orig_list, &dio.dio_orig, diff_flags & DIFF_ICASE);
list_to_diffin(new_list, &dio.dio_new, diff_flags & DIFF_ICASE);
// Compute the diff
int diff_status = diff_file(&dio);
if (diff_status == FAIL)
goto done;
int hunk_idx = 0;
dict_T *hunk_dict;
if (diff_internal_output_fmt == DIFF_INTERNAL_OUTPUT_INDICES)
{
if (rettv_list_alloc(rettv) != OK)
goto done;
list_T *l = rettv->vval.v_list;
// Process each diff hunk
diffhunk_T *hunk = NULL;
while (hunk_idx < dio.dio_diff.dout_ga.ga_len)
{
hunk = ((diffhunk_T **)dio.dio_diff.dout_ga.ga_data)[hunk_idx++];
hunk_dict = get_diff_hunk_indices(hunk);
if (hunk_dict == NULL)
goto done;
list_append_dict(l, hunk_dict);
}
}
else
{
ga_append(&dio.dio_diff.dout_ga, NUL);
rettv->v_type = VAR_STRING;
rettv->vval.v_string =
vim_strsave((char_u *)dio.dio_diff.dout_ga.ga_data);
}
done:
clear_diffin(&dio.dio_new);
if (diff_internal_output_fmt == DIFF_INTERNAL_OUTPUT_INDICES)
clear_diffout(&dio.dio_diff);
else
ga_clear(&dio.dio_diff.dout_ga);
clear_diffin(&dio.dio_orig);
// Restore the 'diffopt' option value.
diff_flags = save_diff_flags;
diff_algorithm = save_diff_algorithm;
diff_internal_output_fmt = save_diff_output_fmt;
#endif
}
#endif

View File

@@ -258,8 +258,9 @@ EXTERN char e_escape_not_allowed_in_digraph[]
EXTERN char e_using_loadkeymap_not_in_sourced_file[]
INIT(= N_("E105: Using :loadkeymap not in a sourced file"));
#endif
// E106 unused
#ifdef FEAT_EVAL
EXTERN char e_unsupported_diff_output_format_str[]
INIT(= N_("E106: Unsupported diff output format: %s"));
EXTERN char e_missing_parenthesis_str[]
INIT(= N_("E107: Missing parentheses: %s"));
EXTERN char e_no_such_variable_str[]

View File

@@ -1148,6 +1148,7 @@ static argcheck_T arg3_buffer_number_number[] = {arg_buffer, arg_number, arg_num
static argcheck_T arg3_buffer_string_any[] = {arg_buffer, arg_string, arg_any};
static argcheck_T arg3_buffer_string_dict[] = {arg_buffer, arg_string, arg_dict_any};
static argcheck_T arg3_dict_number_number[] = {arg_dict_any, arg_number, arg_number};
static argcheck_T arg3_diff[] = {arg_list_string, arg_list_string, arg_dict_any};
static argcheck_T arg3_list_string_dict[] = {arg_list_any, arg_string, arg_dict_any};
static argcheck_T arg3_lnum_number_bool[] = {arg_lnum, arg_number, arg_bool};
static argcheck_T arg3_number[] = {arg_number, arg_number, arg_number};
@@ -1950,6 +1951,8 @@ static funcentry_T global_functions[] =
ret_number_bool, f_deletebufline},
{"did_filetype", 0, 0, 0, NULL,
ret_number_bool, f_did_filetype},
{"diff", 2, 3, FEARG_1, arg3_diff,
ret_list_dict_any, f_diff},
{"diff_filler", 1, 1, FEARG_1, arg1_lnum,
ret_number, f_diff_filler},
{"diff_hlID", 2, 2, FEARG_1, arg2_lnum_number,

View File

@@ -30,4 +30,5 @@ linenr_T diff_get_corresponding_line(buf_T *buf1, linenr_T lnum1);
linenr_T diff_lnum_win(linenr_T lnum, win_T *wp);
void f_diff_filler(typval_T *argvars, typval_T *rettv);
void f_diff_hlID(typval_T *argvars, typval_T *rettv);
void f_diff(typval_T *argvars, typval_T *rettv);
/* vim: set ft=c : */

View File

@@ -26,6 +26,7 @@ int check_for_opt_list_arg(typval_T *args, int idx);
int check_for_dict_arg(typval_T *args, int idx);
int check_for_nonnull_dict_arg(typval_T *args, int idx);
int check_for_opt_dict_arg(typval_T *args, int idx);
int check_for_opt_nonnull_dict_arg(typval_T *args, int idx);
int check_for_chan_or_job_arg(typval_T *args, int idx);
int check_for_opt_chan_or_job_arg(typval_T *args, int idx);
int check_for_job_arg(typval_T *args, int idx);

View File

@@ -1716,5 +1716,182 @@ func Test_diff_put_and_undo()
bwipe!
bwipe!
set nodiff
endfunc
" Test for the diff() function
def Test_diff_func()
# string is added/removed/modified at the beginning
assert_equal("@@ -0,0 +1 @@\n+abc\n",
diff(['def'], ['abc', 'def'], {output: 'unified'}))
assert_equal([{from_idx: 0, from_count: 0, to_idx: 0, to_count: 1}],
diff(['def'], ['abc', 'def'], {output: 'indices'}))
assert_equal("@@ -1 +0,0 @@\n-abc\n",
diff(['abc', 'def'], ['def'], {output: 'unified'}))
assert_equal([{from_idx: 0, from_count: 1, to_idx: 0, to_count: 0}],
diff(['abc', 'def'], ['def'], {output: 'indices'}))
assert_equal("@@ -1 +1 @@\n-abc\n+abx\n",
diff(['abc', 'def'], ['abx', 'def'], {output: 'unified'}))
assert_equal([{from_idx: 0, from_count: 1, to_idx: 0, to_count: 1}],
diff(['abc', 'def'], ['abx', 'def'], {output: 'indices'}))
# string is added/removed/modified at the end
assert_equal("@@ -1,0 +2 @@\n+def\n",
diff(['abc'], ['abc', 'def'], {output: 'unified'}))
assert_equal([{from_idx: 1, from_count: 0, to_idx: 1, to_count: 1}],
diff(['abc'], ['abc', 'def'], {output: 'indices'}))
assert_equal("@@ -2 +1,0 @@\n-def\n",
diff(['abc', 'def'], ['abc'], {output: 'unified'}))
assert_equal([{from_idx: 1, from_count: 1, to_idx: 1, to_count: 0}],
diff(['abc', 'def'], ['abc'], {output: 'indices'}))
assert_equal("@@ -2 +2 @@\n-def\n+xef\n",
diff(['abc', 'def'], ['abc', 'xef'], {output: 'unified'}))
assert_equal([{from_idx: 1, from_count: 1, to_idx: 1, to_count: 1}],
diff(['abc', 'def'], ['abc', 'xef'], {output: 'indices'}))
# string is added/removed/modified in the middle
assert_equal("@@ -2,0 +3 @@\n+xxx\n",
diff(['111', '222', '333'], ['111', '222', 'xxx', '333'], {output: 'unified'}))
assert_equal([{from_idx: 2, from_count: 0, to_idx: 2, to_count: 1}],
diff(['111', '222', '333'], ['111', '222', 'xxx', '333'], {output: 'indices'}))
assert_equal("@@ -3 +2,0 @@\n-333\n",
diff(['111', '222', '333', '444'], ['111', '222', '444'], {output: 'unified'}))
assert_equal([{from_idx: 2, from_count: 1, to_idx: 2, to_count: 0}],
diff(['111', '222', '333', '444'], ['111', '222', '444'], {output: 'indices'}))
assert_equal("@@ -3 +3 @@\n-333\n+xxx\n",
diff(['111', '222', '333', '444'], ['111', '222', 'xxx', '444'], {output: 'unified'}))
assert_equal([{from_idx: 2, from_count: 1, to_idx: 2, to_count: 1}],
diff(['111', '222', '333', '444'], ['111', '222', 'xxx', '444'], {output: 'indices'}))
# new strings are added to an empty List
assert_equal("@@ -0,0 +1,2 @@\n+abc\n+def\n",
diff([], ['abc', 'def'], {output: 'unified'}))
assert_equal([{from_idx: 0, from_count: 0, to_idx: 0, to_count: 2}],
diff([], ['abc', 'def'], {output: 'indices'}))
# all the strings are removed from a List
assert_equal("@@ -1,2 +0,0 @@\n-abc\n-def\n",
diff(['abc', 'def'], [], {output: 'unified'}))
assert_equal([{from_idx: 0, from_count: 2, to_idx: 0, to_count: 0}],
diff(['abc', 'def'], [], {output: 'indices'}))
# First character is added/removed/different
assert_equal("@@ -1 +1 @@\n-abc\n+bc\n",
diff(['abc'], ['bc'], {output: 'unified'}))
assert_equal([{from_idx: 0, from_count: 1, to_idx: 0, to_count: 1}],
diff(['abc'], ['bc'], {output: 'indices'}))
assert_equal("@@ -1 +1 @@\n-bc\n+abc\n",
diff(['bc'], ['abc'], {output: 'unified'}))
assert_equal([{from_idx: 0, from_count: 1, to_idx: 0, to_count: 1}],
diff(['bc'], ['abc'], {output: 'indices'}))
assert_equal("@@ -1 +1 @@\n-abc\n+xbc\n",
diff(['abc'], ['xbc'], {output: 'unified'}))
assert_equal([{from_idx: 0, from_count: 1, to_idx: 0, to_count: 1}],
diff(['abc'], ['xbc'], {output: 'indices'}))
# Last character is added/removed/different
assert_equal("@@ -1 +1 @@\n-abc\n+abcd\n",
diff(['abc'], ['abcd'], {output: 'unified'}))
assert_equal([{from_idx: 0, from_count: 1, to_idx: 0, to_count: 1}],
diff(['abc'], ['abcd'], {output: 'indices'}))
assert_equal("@@ -1 +1 @@\n-abcd\n+abc\n",
diff(['abcd'], ['abc'], {output: 'unified'}))
assert_equal([{from_idx: 0, from_count: 1, to_idx: 0, to_count: 1}],
diff(['abcd'], ['abc'], {output: 'indices'}))
assert_equal("@@ -1 +1 @@\n-abc\n+abx\n",
diff(['abc'], ['abx'], {output: 'unified'}))
assert_equal([{from_idx: 0, from_count: 1, to_idx: 0, to_count: 1}],
diff(['abc'], ['abx'], {output: 'indices'}))
# partial string modification at the start and at the end.
var fromlist =<< trim END
one two
three four
five six
END
var tolist =<< trim END
one
six
END
assert_equal("@@ -1,3 +1,2 @@\n-one two\n-three four\n-five six\n+one\n+six\n", diff(fromlist, tolist, {output: 'unified'}))
assert_equal([{from_idx: 0, from_count: 3, to_idx: 0, to_count: 2}],
diff(fromlist, tolist, {output: 'indices'}))
# non-contiguous modifications
fromlist =<< trim END
one two
three four
five abc six
END
tolist =<< trim END
one abc two
three four
five six
END
assert_equal("@@ -1 +1 @@\n-one two\n+one abc two\n@@ -3 +3 @@\n-five abc six\n+five six\n",
diff(fromlist, tolist, {output: 'unified'}))
assert_equal([{from_idx: 0, from_count: 1, to_idx: 0, to_count: 1},
{from_idx: 2, from_count: 1, to_idx: 2, to_count: 1}],
diff(fromlist, tolist, {output: 'indices'}))
# add/remove blank lines
assert_equal("@@ -2,2 +1,0 @@\n-\n-\n",
diff(['one', '', '', 'two'], ['one', 'two'], {output: 'unified'}))
assert_equal([{from_idx: 1, from_count: 2, to_idx: 1, to_count: 0}],
diff(['one', '', '', 'two'], ['one', 'two'], {output: 'indices'}))
assert_equal("@@ -1,0 +2,2 @@\n+\n+\n",
diff(['one', 'two'], ['one', '', '', 'two'], {output: 'unified'}))
assert_equal([{'from_idx': 1, 'from_count': 0, 'to_idx': 1, 'to_count': 2}],
diff(['one', 'two'], ['one', '', '', 'two'], {output: 'indices'}))
# diff ignoring case
assert_equal('', diff(['abc', 'def'], ['ABC', 'DEF'], {icase: true, output: 'unified'}))
assert_equal([], diff(['abc', 'def'], ['ABC', 'DEF'], {icase: true, output: 'indices'}))
# diff ignoring all whitespace changes except leading whitespace changes
assert_equal('', diff(['abc def'], ['abc def '], {iwhite: true}))
assert_equal("@@ -1 +1 @@\n- abc\n+ xxx\n", diff([' abc'], [' xxx'], {iwhite: v:true}))
assert_equal("@@ -1 +1 @@\n- abc\n+ xxx\n", diff([' abc'], [' xxx'], {iwhite: v:false}))
assert_equal("@@ -1 +1 @@\n-abc \n+xxx \n", diff(['abc '], ['xxx '], {iwhite: v:true}))
assert_equal("@@ -1 +1 @@\n-abc \n+xxx \n", diff(['abc '], ['xxx '], {iwhite: v:false}))
# diff ignoring all whitespace changes
assert_equal('', diff(['abc def'], [' abc def '], {iwhiteall: true}))
assert_equal("@@ -1 +1 @@\n- abc \n+ xxx \n", diff([' abc '], [' xxx '], {iwhiteall: v:true}))
assert_equal("@@ -1 +1 @@\n- abc \n+ xxx \n", diff([' abc '], [' xxx '], {iwhiteall: v:false}))
# diff ignoring trailing whitespace changes
assert_equal('', diff(['abc'], ['abc '], {iwhiteeol: true}))
# diff ignoring blank lines
assert_equal('', diff(['one', '', '', 'two'], ['one', 'two'], {iblank: true}))
assert_equal('', diff(['one', 'two'], ['one', '', '', 'two'], {iblank: true}))
# same string
assert_equal('', diff(['abc', 'def', 'ghi'], ['abc', 'def', 'ghi']))
assert_equal('', diff([''], ['']))
assert_equal('', diff([], []))
# different xdiff algorithms
for algo in ['myers', 'minimal', 'patience', 'histogram']
assert_equal("@@ -1 +1 @@\n- abc \n+ xxx \n",
diff([' abc '], [' xxx '], {algorithm: algo, output: 'unified'}))
assert_equal([{from_idx: 0, from_count: 1, to_idx: 0, to_count: 1}],
diff([' abc '], [' xxx '], {algorithm: algo, output: 'indices'}))
endfor
assert_equal("@@ -1 +1 @@\n- abc \n+ xxx \n",
diff([' abc '], [' xxx '], {indent-heuristic: true, output: 'unified'}))
assert_equal([{from_idx: 0, from_count: 1, to_idx: 0, to_count: 1}],
diff([' abc '], [' xxx '], {indent-heuristic: true, output: 'indices'}))
# identical strings
assert_equal('', diff(['111', '222'], ['111', '222'], {output: 'unified'}))
assert_equal([], diff(['111', '222'], ['111', '222'], {output: 'indices'}))
assert_equal('', diff([], [], {output: 'unified'}))
assert_equal([], diff([], [], {output: 'indices'}))
# Error cases
assert_fails('call diff({}, ["a"])', 'E1211:')
assert_fails('call diff(["a"], {})', 'E1211:')
assert_fails('call diff(["a"], ["a"], [])', 'E1206:')
assert_fails('call diff(["a"], ["a"], {output: "xyz"})', 'E106: Unsupported diff output format: xyz')

View File

@@ -623,6 +623,16 @@ check_for_opt_dict_arg(typval_T *args, int idx)
|| check_for_dict_arg(args, idx) != FAIL) ? OK : FAIL;
}
/*
* Check for an optional non-NULL dict argument at 'idx'
*/
int
check_for_opt_nonnull_dict_arg(typval_T *args, int idx)
{
return (args[idx].v_type == VAR_UNKNOWN
|| check_for_nonnull_dict_arg(args, idx) != FAIL) ? OK : FAIL;
}
#if defined(FEAT_JOB_CHANNEL) || defined(PROTO)
/*
* Give an error and return FAIL unless "args[idx]" is a channel or a job.

View File

@@ -704,6 +704,8 @@ static char *(features[]) =
static int included_patches[] =
{ /* Add new patch number below this line */
/**/
71,
/**/
70,
/**/