changeset 34324:6d9f59e88fc2 v9.1.0096

patch 9.1.0096: diff() function uses 'diffexpr' Commit: https://github.com/vim/vim/commit/be156a31c5400eb12025e480c477b1df88244801 Author: Yegappan Lakshmanan <yegappan@yahoo.com> Date: Sun Feb 11 17:08:29 2024 +0100 patch 9.1.0096: diff() function uses 'diffexpr' Problem: diff() function uses 'diffexpr' (rickhowe) Solution: Make diff() always use internal diff(), add support for unified diff context length, sort diff() options in help (Yegappan Lakshmanan) fixes: #13989 closes: #14010 Signed-off-by: Yegappan Lakshmanan <yegappan@yahoo.com> Signed-off-by: Christian Brabandt <cb@256bit.org>
author Christian Brabandt <cb@256bit.org>
date Sun, 11 Feb 2024 17:15:05 +0100
parents 857f6a19ca6a
children 9cc43f955f1b
files runtime/doc/builtin.txt src/diff.c src/testdir/test_diffmode.vim src/version.c
diffstat 4 files changed, 95 insertions(+), 46 deletions(-) [+]
line wrap: on
line diff
--- a/runtime/doc/builtin.txt
+++ b/runtime/doc/builtin.txt
@@ -1,4 +1,4 @@
-*builtin.txt*	For Vim version 9.1.  Last change: 2024 Feb 01
+*builtin.txt*	For Vim version 9.1.  Last change: 2024 Feb 11
 
 
 		  VIM REFERENCE MANUAL	  by Bram Moolenaar
@@ -2074,20 +2074,22 @@ diff({fromlist}, {tolist} [, {options}])
 
 		The {options} Dict argument also specifies diff options
 		(similar to 'diffopt') and supports the following items:
+		    algorithm		Dict specifying the diff algorithm to
+					use.  Supported boolean items are
+					"myers", "minimal", "patience" and
+					"histogram".
+		    context		unified diff context length.  Default
+					is 1.
 		    iblank		ignore changes where lines are all
 					blank.
 		    icase		ignore changes in case of text.
+		    indent-heuristic	use the indent heuristic for the
+					internal diff library.
 		    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
--- a/src/diff.c
+++ b/src/diff.c
@@ -42,10 +42,6 @@ static int	diff_flags = DIFF_INTERNAL | 
 
 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
@@ -76,12 +72,19 @@ typedef struct {
     long     count_new;
 } diffhunk_T;
 
+typedef enum {
+    DIO_OUTPUT_INDICES = 0,	// default
+    DIO_OUTPUT_UNIFIED = 1	// unified diff format
+} dio_outfmt_T;
+
 // two diff inputs and one result
 typedef struct {
-    diffin_T	dio_orig;     // original file input
-    diffin_T	dio_new;      // new file input
-    diffout_T	dio_diff;     // diff result
-    int		dio_internal; // using internal diff
+    diffin_T	    dio_orig;     // original file input
+    diffin_T	    dio_new;      // new file input
+    diffout_T	    dio_diff;     // diff result
+    int		    dio_internal; // using internal diff
+    dio_outfmt_T    dio_outfmt;   // internal diff output format
+    int		    dio_ctxlen;   // unified diff context length
 } diffio_T;
 
 static int diff_buf_idx(buf_T *buf);
@@ -1145,9 +1148,9 @@ diff_file_internal(diffio_T *diffio)
     if (diff_flags & DIFF_IBLANK)
 	param.flags |= XDF_IGNORE_BLANK_LINES;
 
-    emit_cfg.ctxlen = 0; // don't need any diff_context here
+    emit_cfg.ctxlen = diffio->dio_ctxlen;
     emit_cb.priv = &diffio->dio_diff;
-    if (diff_internal_output_fmt == DIFF_INTERNAL_OUTPUT_INDICES)
+    if (diffio->dio_outfmt == DIO_OUTPUT_INDICES)
 	emit_cfg.hunk_func = xdiff_out_indices;
     else
 	emit_cb.out_line = xdiff_out_unified;
@@ -3473,10 +3476,11 @@ f_diff_hlID(typval_T *argvars UNUSED, ty
  */
     static int
 parse_diff_optarg(
-    typval_T	*opts,
-    int		*diffopts,
-    long	*diffalgo,
-    int		*diff_output_fmt)
+    typval_T	    *opts,
+    int		    *diffopts,
+    long	    *diffalgo,
+    dio_outfmt_T    *diff_output_fmt,
+    int		    *diff_ctxlen)
 {
     dict_T *d = opts->vval.v_dict;
 
@@ -3497,9 +3501,9 @@ parse_diff_optarg(
     if (output_fmt != NULL)
     {
 	if (STRNCMP(output_fmt, "unified", 7) == 0)
-	    *diff_output_fmt = DIFF_INTERNAL_OUTPUT_UNIFIED;
+	    *diff_output_fmt = DIO_OUTPUT_UNIFIED;
 	else if (STRNCMP(output_fmt, "indices", 7) == 0)
-	    *diff_output_fmt = DIFF_INTERNAL_OUTPUT_INDICES;
+	    *diff_output_fmt = DIO_OUTPUT_INDICES;
 	else
 	{
 	    semsg(_(e_unsupported_diff_output_format_str), output_fmt);
@@ -3507,6 +3511,10 @@ parse_diff_optarg(
 	}
     }
 
+    *diff_ctxlen = dict_get_number_def(d, "context", 1);
+    if (*diff_ctxlen < 0)
+	*diff_ctxlen = 1;
+
     if (dict_get_bool(d, "iblank", FALSE))
 	*diffopts |= DIFF_IBLANK;
     if (dict_get_bool(d, "icase", FALSE))
@@ -3602,33 +3610,43 @@ f_diff(typval_T *argvars UNUSED, typval_
     // 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;
+    dio.dio_outfmt = DIO_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;
+			&dio.dio_outfmt, &dio.dio_ctxlen) == FAIL)
 	    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);
 
+    // If 'diffexpr' is set, then the internal diff is not used.  Set
+    // 'diffexpr' to an empty string temporarily.
+    int	    restore_diffexpr = FALSE;
+    char_u  cc = *p_dex;
+    if (*p_dex != NUL)
+    {
+	restore_diffexpr = TRUE;
+	*p_dex = NUL;
+    }
+
     // Compute the diff
     int diff_status = diff_file(&dio);
 
+    // restore 'diffexpr'
+    if (restore_diffexpr)
+	*p_dex = cc;
+
     if (diff_status == FAIL)
 	goto done;
 
     int		hunk_idx = 0;
     dict_T	*hunk_dict;
 
-    if (diff_internal_output_fmt == DIFF_INTERNAL_OUTPUT_INDICES)
+    if (dio.dio_outfmt == DIO_OUTPUT_INDICES)
     {
 	if (rettv_list_alloc(rettv) != OK)
 	    goto done;
@@ -3657,7 +3675,7 @@ f_diff(typval_T *argvars UNUSED, typval_
 
 done:
     clear_diffin(&dio.dio_new);
-    if (diff_internal_output_fmt == DIFF_INTERNAL_OUTPUT_INDICES)
+    if (dio.dio_outfmt == DIO_OUTPUT_INDICES)
 	clear_diffout(&dio.dio_diff);
     else
 	ga_clear(&dio.dio_diff.dout_ga);
@@ -3665,7 +3683,6 @@ done:
     // Restore the 'diffopt' option value.
     diff_flags = save_diff_flags;
     diff_algorithm = save_diff_algorithm;
-    diff_internal_output_fmt = save_diff_output_fmt;
 # endif
 }
 
--- a/src/testdir/test_diffmode.vim
+++ b/src/testdir/test_diffmode.vim
@@ -1719,43 +1719,43 @@ 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",
+  assert_equal("@@ -1 +1,2 @@\n+abc\n def\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",
+  assert_equal("@@ -1,2 +1 @@\n-abc\n def\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",
+  assert_equal("@@ -1,2 +1,2 @@\n-abc\n+abx\n def\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",
+  assert_equal("@@ -1 +1,2 @@\n abc\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",
+  assert_equal("@@ -1,2 +1 @@\n abc\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",
+  assert_equal("@@ -1,2 +1,2 @@\n abc\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",
+  assert_equal("@@ -2,2 +2,3 @@\n 222\n+xxx\n 333\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",
+  assert_equal("@@ -2,3 +2,2 @@\n 222\n-333\n 444\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",
+  assert_equal("@@ -2,3 +2,3 @@\n 222\n-333\n+xxx\n 444\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'}))
@@ -1825,18 +1825,17 @@ def Test_diff_func()
     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",
+  assert_equal("@@ -1,3 +1,3 @@\n-one two\n+one abc two\n three four\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}],
+  assert_equal([{from_idx: 0, from_count: 3, to_idx: 0, to_count: 3}],
                diff(fromlist, tolist, {output: 'indices'}))
 
   # add/remove blank lines
-  assert_equal("@@ -2,2 +1,0 @@\n-\n-\n",
+  assert_equal("@@ -1,4 +1,2 @@\n one\n-\n-\n two\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",
+  assert_equal("@@ -1,2 +1,4 @@\n one\n+\n+\n two\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'}))
@@ -1887,11 +1886,40 @@ def Test_diff_func()
   assert_equal('', diff([], [], {output: 'unified'}))
   assert_equal([], diff([], [], {output: 'indices'}))
 
+  # If 'diffexpr' is set, it should not be used for diff()
+  def MyDiffExpr()
+  enddef
+  var save_diffexpr = &diffexpr
+  :set diffexpr=MyDiffExpr()
+  assert_equal("@@ -1 +1 @@\n-abc\n+\n",
+               diff(['abc'], [''], {output: 'unified'}))
+  assert_equal([{'from_idx': 0, 'from_count': 1, 'to_idx': 0, 'to_count': 1}],
+               diff(['abc'], [''], {output: 'indices'}))
+  assert_equal('MyDiffExpr()', &diffexpr)
+  &diffexpr = save_diffexpr
+
+  # try different values for unified diff 'context'
+  assert_equal("@@ -0,0 +1 @@\n+x\n",
+               diff(['a', 'b', 'c'], ['x', 'a', 'b', 'c']))
+  assert_equal("@@ -0,0 +1 @@\n+x\n",
+               diff(['a', 'b', 'c'], ['x', 'a', 'b', 'c'], {context: 0}))
+  assert_equal("@@ -1 +1,2 @@\n+x\n a\n",
+               diff(['a', 'b', 'c'], ['x', 'a', 'b', 'c'], {context: 1}))
+  assert_equal("@@ -1,2 +1,3 @@\n+x\n a\n b\n",
+               diff(['a', 'b', 'c'], ['x', 'a', 'b', 'c'], {context: 2}))
+  assert_equal("@@ -1,3 +1,4 @@\n+x\n a\n b\n c\n",
+               diff(['a', 'b', 'c'], ['x', 'a', 'b', 'c'], {context: 3}))
+  assert_equal("@@ -1,3 +1,4 @@\n+x\n a\n b\n c\n",
+               diff(['a', 'b', 'c'], ['x', 'a', 'b', 'c'], {context: 4}))
+  assert_equal("@@ -1 +1,2 @@\n+x\n a\n",
+               diff(['a', 'b', 'c'], ['x', 'a', 'b', 'c'], {context: -1}))
+
   # 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')
+  assert_fails('call diff(["a"], ["a"], {context: []})', 'E745: Using a List as a Number')
 enddef
 
 " vim: shiftwidth=2 sts=2 expandtab
--- a/src/version.c
+++ b/src/version.c
@@ -705,6 +705,8 @@ static char *(features[]) =
 static int included_patches[] =
 {   /* Add new patch number below this line */
 /**/
+    96,
+/**/
     95,
 /**/
     94,