0
0
mirror of https://github.com/vim/vim.git synced 2025-09-23 03:43:49 -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

@@ -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