summaryrefslogtreecommitdiff
path: root/src/diff.c
diff options
context:
space:
mode:
authorBram Moolenaar <Bram@vim.org>2018-09-10 17:51:58 +0200
committerBram Moolenaar <Bram@vim.org>2018-09-10 17:51:58 +0200
commite828b7621cf9065a3582be0c4dd1e0e846e335bf (patch)
tree79cf05b6295837108fb6edbbc154e333c940698a /src/diff.c
parent93a1df2c205c8399d96c172d9483e0793d32892a (diff)
downloadvim-git-e828b7621cf9065a3582be0c4dd1e0e846e335bf.tar.gz
patch 8.1.0360: using an external diff program is slow and inflexiblev8.1.0360
Problem: Using an external diff program is slow and inflexible. Solution: Include the xdiff library. (Christian Brabandt, closes #2732) Use it by default.
Diffstat (limited to 'src/diff.c')
-rw-r--r--src/diff.c813
1 files changed, 632 insertions, 181 deletions
diff --git a/src/diff.c b/src/diff.c
index c67654f62..4c0792baa 100644
--- a/src/diff.c
+++ b/src/diff.c
@@ -9,23 +9,32 @@
/*
* diff.c: code for diff'ing two, three or four buffers.
+ *
+ * There are three ways to diff:
+ * - Shell out to an external diff program, using files.
+ * - Use the compiled-in xdiff library.
+ * - Let 'diffexpr' do the work, using files.
*/
#include "vim.h"
+#include "xdiff/xdiff.h"
#if defined(FEAT_DIFF) || defined(PROTO)
static int diff_busy = FALSE; /* ex_diffgetput() is busy */
/* flags obtained from the 'diffopt' option */
-#define DIFF_FILLER 1 /* display filler lines */
-#define DIFF_ICASE 2 /* ignore case */
-#define DIFF_IWHITE 4 /* ignore change in white space */
-#define DIFF_HORIZONTAL 8 /* horizontal splits */
-#define DIFF_VERTICAL 16 /* vertical splits */
-#define DIFF_HIDDEN_OFF 32 /* diffoff when hidden */
+#define DIFF_FILLER 1 // display filler lines
+#define DIFF_ICASE 2 // ignore case
+#define DIFF_IWHITE 4 // ignore change in white space
+#define DIFF_HORIZONTAL 8 // horizontal splits
+#define DIFF_VERTICAL 16 // vertical splits
+#define DIFF_HIDDEN_OFF 32 // diffoff when hidden
+#define DIFF_INTERNAL 64 // use internal xdiff algorithm
static int diff_flags = DIFF_FILLER;
+static long diff_algorithm = 0;
+
#define LBUFLEN 50 /* length of line in diff file */
static int diff_a_works = MAYBE; /* TRUE when "diff -a" works, FALSE when it
@@ -36,22 +45,45 @@ static int diff_bin_works = MAYBE; /* TRUE when "diff --binary" works, FALSE
checked yet */
#endif
+// used for diff input
+typedef struct {
+ char_u *din_fname; // used for external diff
+ mmfile_t din_mmfile; // used for internal diff
+} diffin_T;
+
+// used for diff result
+typedef struct {
+ char_u *dout_fname; // used for external diff
+ garray_T dout_ga; // used for internal diff
+} diffout_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
+} diffio_T;
+
static int diff_buf_idx(buf_T *buf);
static int diff_buf_idx_tp(buf_T *buf, tabpage_T *tp);
static void diff_mark_adjust_tp(tabpage_T *tp, int idx, linenr_T line1, linenr_T line2, long amount, long amount_after);
static void diff_check_unchanged(tabpage_T *tp, diff_T *dp);
static int diff_check_sanity(tabpage_T *tp, diff_T *dp);
static void diff_redraw(int dofold);
-static int diff_write(buf_T *buf, char_u *fname);
-static void diff_file(char_u *tmp_orig, char_u *tmp_new, char_u *tmp_diff);
+static int check_external_diff(diffio_T *diffio);
+static int diff_file(diffio_T *diffio);
static int diff_equal_entry(diff_T *dp, int idx1, int idx2);
static int diff_cmp(char_u *s1, char_u *s2);
#ifdef FEAT_FOLDING
static void diff_fold_update(diff_T *dp, int skip_idx);
#endif
-static void diff_read(int idx_orig, int idx_new, char_u *fname);
+static void diff_read(int idx_orig, int idx_new, diffout_T *fname);
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, linenr_T *lnum_orig, long *count_orig, linenr_T *lnum_new, long *count_new);
+static int parse_diff_unified(char_u *line, linenr_T *lnum_orig, long *count_orig, linenr_T *lnum_new, long *count_new);
+static int xdiff_out(void *priv, mmbuffer_t *mb, int nbuf);
#ifndef USE_CR
# define tag_fgets vim_fgets
@@ -631,81 +663,290 @@ diff_redraw(
}
}
+ static void
+clear_diffin(diffin_T *din)
+{
+ if (din->din_fname == NULL)
+ {
+ vim_free(din->din_mmfile.ptr);
+ din->din_mmfile.ptr = NULL;
+ }
+ else
+ mch_remove(din->din_fname);
+}
+
+ static void
+clear_diffout(diffout_T *dout)
+{
+ if (dout->dout_fname == NULL)
+ ga_clear_strings(&dout->dout_ga);
+ else
+ mch_remove(dout->dout_fname);
+}
+
+/*
+ * Write buffer "buf" to a memory buffer.
+ * Return FAIL for failure.
+ */
+ static int
+diff_write_buffer(buf_T *buf, diffin_T *din)
+{
+ linenr_T lnum;
+ char_u *s;
+ long len = 0;
+ char_u *ptr;
+
+ // xdiff requires one big block of memory with all the text.
+ for (lnum = 1; lnum <= buf->b_ml.ml_line_count; ++lnum)
+ len += STRLEN(ml_get_buf(buf, lnum, FALSE)) + 1;
+ ptr = lalloc(len, TRUE);
+ if (ptr == NULL)
+ {
+ // Allocating memory failed. This can happen, because we try to read
+ // the whole buffer text into memory. Set the failed flag, the diff
+ // will be retried with external diff. The flag is never reset.
+ buf->b_diff_failed = TRUE;
+ if (p_verbose > 0)
+ {
+ verbose_enter();
+ smsg((char_u *)
+ _("Not enough memory to use internal diff for buffer \"%s\""),
+ buf->b_fname);
+ verbose_leave();
+ }
+ return FAIL;
+ }
+ din->din_mmfile.ptr = (char *)ptr;
+ din->din_mmfile.size = len;
+
+ len = 0;
+ for (lnum = 1; lnum <= buf->b_ml.ml_line_count; ++lnum)
+ {
+ for (s = ml_get_buf(buf, lnum, FALSE); *s != NUL; )
+ {
+ if (diff_flags & DIFF_ICASE)
+ {
+ int c;
+
+ // xdiff doesn't support ignoring case, fold-case the text.
+#ifdef FEAT_MBYTE
+ int orig_len;
+ char_u cbuf[MB_MAXBYTES + 1];
+
+ c = PTR2CHAR(s);
+ c = enc_utf8 ? utf_fold(c) : MB_TOLOWER(c);
+ orig_len = MB_PTR2LEN(s);
+ if (mb_char2bytes(c, cbuf) != orig_len)
+ // TODO: handle byte length difference
+ mch_memmove(ptr + len, s, orig_len);
+ else
+ mch_memmove(ptr + len, cbuf, orig_len);
+
+ s += orig_len;
+ len += orig_len;
+#else
+ c = *s++;
+ ptr[len++] = TOLOWER_LOC(c);
+#endif
+ }
+ else
+ ptr[len++] = *s++;
+ }
+ ptr[len++] = NL;
+ }
+ return OK;
+}
+
/*
- * Write buffer "buf" to file "name".
- * Always use 'fileformat' set to "unix".
- * Return FAIL for failure
+ * Write buffer "buf" to file or memory buffer.
+ * Return FAIL for failure.
*/
static int
-diff_write(buf_T *buf, char_u *fname)
+diff_write(buf_T *buf, diffin_T *din)
{
int r;
char_u *save_ff;
+ if (din->din_fname == NULL)
+ return diff_write_buffer(buf, din);
+
+ // Always use 'fileformat' set to "unix".
save_ff = buf->b_p_ff;
buf->b_p_ff = vim_strsave((char_u *)FF_UNIX);
- r = buf_write(buf, fname, NULL, (linenr_T)1, buf->b_ml.ml_line_count,
- NULL, FALSE, FALSE, FALSE, TRUE);
+ r = buf_write(buf, din->din_fname, NULL,
+ (linenr_T)1, buf->b_ml.ml_line_count,
+ NULL, FALSE, FALSE, FALSE, TRUE);
free_string_option(buf->b_p_ff);
buf->b_p_ff = save_ff;
return r;
}
/*
+ * Update the diffs for all buffers involved.
+ */
+ static void
+diff_try_update(
+ diffio_T *dio,
+ int idx_orig,
+ exarg_T *eap) // "eap" can be NULL
+{
+ buf_T *buf;
+ int idx_new;
+
+ if (dio->dio_internal)
+ {
+ ga_init2(&dio->dio_diff.dout_ga, sizeof(char *), 1000);
+ }
+ else
+ {
+ // We need three temp file names.
+ dio->dio_orig.din_fname = vim_tempname('o', TRUE);
+ dio->dio_new.din_fname = vim_tempname('n', TRUE);
+ dio->dio_diff.dout_fname = vim_tempname('d', TRUE);
+ if (dio->dio_orig.din_fname == NULL
+ || dio->dio_new.din_fname == NULL
+ || dio->dio_diff.dout_fname == NULL)
+ goto theend;
+ }
+
+ // Check external diff is actually working.
+ if (!dio->dio_internal && check_external_diff(dio) == FAIL)
+ goto theend;
+
+ // :diffupdate!
+ if (eap != NULL && eap->forceit)
+ for (idx_new = idx_orig; idx_new < DB_COUNT; ++idx_new)
+ {
+ buf = curtab->tp_diffbuf[idx_new];
+ if (buf_valid(buf))
+ buf_check_timestamp(buf, FALSE);
+ }
+
+ // Write the first buffer to a tempfile or mmfile_t.
+ buf = curtab->tp_diffbuf[idx_orig];
+ if (diff_write(buf, &dio->dio_orig) == FAIL)
+ goto theend;
+
+ // Make a difference between the first buffer and every other.
+ for (idx_new = idx_orig + 1; idx_new < DB_COUNT; ++idx_new)
+ {
+ buf = curtab->tp_diffbuf[idx_new];
+ if (buf == NULL || buf->b_ml.ml_mfp == NULL)
+ continue; // skip buffer that isn't loaded
+
+ // Write the other buffer and diff with the first one.
+ if (diff_write(buf, &dio->dio_new) == FAIL)
+ continue;
+ if (diff_file(dio) == FAIL)
+ continue;
+
+ // Read the diff output and add each entry to the diff list.
+ diff_read(idx_orig, idx_new, &dio->dio_diff);
+
+ clear_diffin(&dio->dio_new);
+ clear_diffout(&dio->dio_diff);
+ }
+ clear_diffin(&dio->dio_orig);
+
+theend:
+ vim_free(dio->dio_orig.din_fname);
+ vim_free(dio->dio_new.din_fname);
+ vim_free(dio->dio_diff.dout_fname);
+}
+
+/*
+ * Return TRUE if the options are set to use the internal diff library.
+ * Note that if the internal diff failed for one of the buffers, the external
+ * diff will be used anyway.
+ */
+ static int
+diff_internal(void)
+{
+ return (diff_flags & DIFF_INTERNAL) != 0 && *p_dex == NUL;
+}
+
+/*
+ * Return TRUE if the internal diff failed for one of the diff buffers.
+ */
+ static int
+diff_internal_failed(void)
+{
+ int idx;
+
+ // Only need to do something when there is another buffer.
+ for (idx = 0; idx < DB_COUNT; ++idx)
+ if (curtab->tp_diffbuf[idx] != NULL
+ && curtab->tp_diffbuf[idx]->b_diff_failed)
+ return TRUE;
+ return FALSE;
+}
+
+/*
* Completely update the diffs for the buffers involved.
* This uses the ordinary "diff" command.
* The buffers are written to a file, also for unmodified buffers (the file
* could have been produced by autocommands, e.g. the netrw plugin).
*/
void
-ex_diffupdate(
- exarg_T *eap) /* can be NULL */
+ex_diffupdate(exarg_T *eap) // "eap" can be NULL
{
- buf_T *buf;
int idx_orig;
int idx_new;
- char_u *tmp_orig;
- char_u *tmp_new;
- char_u *tmp_diff;
- FILE *fd;
- int ok;
- int io_error = FALSE;
+ diffio_T diffio;
- /* Delete all diffblocks. */
+ // Delete all diffblocks.
diff_clear(curtab);
curtab->tp_diff_invalid = FALSE;
- /* Use the first buffer as the original text. */
+ // Use the first buffer as the original text.
for (idx_orig = 0; idx_orig < DB_COUNT; ++idx_orig)
if (curtab->tp_diffbuf[idx_orig] != NULL)
break;
if (idx_orig == DB_COUNT)
return;
- /* Only need to do something when there is another buffer. */
+ // Only need to do something when there is another buffer.
for (idx_new = idx_orig + 1; idx_new < DB_COUNT; ++idx_new)
if (curtab->tp_diffbuf[idx_new] != NULL)
break;
if (idx_new == DB_COUNT)
return;
- /* We need three temp file names. */
- tmp_orig = vim_tempname('o', TRUE);
- tmp_new = vim_tempname('n', TRUE);
- tmp_diff = vim_tempname('d', TRUE);
- if (tmp_orig == NULL || tmp_new == NULL || tmp_diff == NULL)
- goto theend;
+ // Only use the internal method if it did not fail for one of the buffers.
+ vim_memset(&diffio, 0, sizeof(diffio));
+ diffio.dio_internal = diff_internal() && !diff_internal_failed();
+
+ diff_try_update(&diffio, idx_orig, eap);
+ if (diffio.dio_internal && diff_internal_failed())
+ {
+ // Internal diff failed, use external diff instead.
+ vim_memset(&diffio, 0, sizeof(diffio));
+ diff_try_update(&diffio, idx_orig, eap);
+ }
+
+ // force updating cursor position on screen
+ curwin->w_valid_cursor.lnum = 0;
+
+ diff_redraw(TRUE);
+}
+
+/*
+ * Do a quick test if "diff" really works. Otherwise it looks like there
+ * are no differences. Can't use the return value, it's non-zero when
+ * there are differences.
+ */
+ static int
+check_external_diff(diffio_T *diffio)
+{
+ FILE *fd;
+ int ok;
+ int io_error = FALSE;
- /*
- * Do a quick test if "diff" really works. Otherwise it looks like there
- * are no differences. Can't use the return value, it's non-zero when
- * there are differences.
- * May try twice, first with "-a" and then without.
- */
+ // May try twice, first with "-a" and then without.
for (;;)
{
ok = FALSE;
- fd = mch_fopen((char *)tmp_orig, "w");
+ fd = mch_fopen((char *)diffio->dio_orig.din_fname, "w");
if (fd == NULL)
io_error = TRUE;
else
@@ -713,7 +954,7 @@ ex_diffupdate(
if (fwrite("line1\n", (size_t)6, (size_t)1, fd) != 1)
io_error = TRUE;
fclose(fd);
- fd = mch_fopen((char *)tmp_new, "w");
+ fd = mch_fopen((char *)diffio->dio_new.din_fname, "w");
if (fd == NULL)
io_error = TRUE;
else
@@ -721,8 +962,9 @@ ex_diffupdate(
if (fwrite("line2\n", (size_t)6, (size_t)1, fd) != 1)
io_error = TRUE;
fclose(fd);
- diff_file(tmp_orig, tmp_new, tmp_diff);
- fd = mch_fopen((char *)tmp_diff, "r");
+ fd = NULL;
+ if (diff_file(diffio) == OK)
+ fd = mch_fopen((char *)diffio->dio_diff.dout_fname, "r");
if (fd == NULL)
io_error = TRUE;
else
@@ -739,10 +981,10 @@ ex_diffupdate(
}
fclose(fd);
}
- mch_remove(tmp_diff);
- mch_remove(tmp_new);
+ mch_remove(diffio->dio_diff.dout_fname);
+ mch_remove(diffio->dio_new.din_fname);
}
- mch_remove(tmp_orig);
+ mch_remove(diffio->dio_orig.din_fname);
}
#ifdef FEAT_EVAL
@@ -785,98 +1027,101 @@ ex_diffupdate(
#if defined(MSWIN)
diff_bin_works = MAYBE;
#endif
- goto theend;
+ return FAIL;
}
+ return OK;
+}
- /* :diffupdate! */
- if (eap != NULL && eap->forceit)
- for (idx_new = idx_orig; idx_new < DB_COUNT; ++idx_new)
- {
- buf = curtab->tp_diffbuf[idx_new];
- if (buf_valid(buf))
- buf_check_timestamp(buf, FALSE);
- }
-
- /* Write the first buffer to a tempfile. */
- buf = curtab->tp_diffbuf[idx_orig];
- if (diff_write(buf, tmp_orig) == FAIL)
- goto theend;
-
- /* Make a difference between the first buffer and every other. */
- for (idx_new = idx_orig + 1; idx_new < DB_COUNT; ++idx_new)
- {
- buf = curtab->tp_diffbuf[idx_new];
- if (buf == NULL || buf->b_ml.ml_mfp == NULL)
- continue; /* skip buffer that isn't loaded */
- if (diff_write(buf, tmp_new) == FAIL)
- continue;
- diff_file(tmp_orig, tmp_new, tmp_diff);
+/*
+ * Invoke the xdiff function.
+ */
+ static int
+diff_file_internal(diffio_T *diffio)
+{
+ xpparam_t param;
+ xdemitconf_t emit_cfg;
+ xdemitcb_t emit_cb;
- /* Read the diff output and add each entry to the diff list. */
- diff_read(idx_orig, idx_new, tmp_diff);
- mch_remove(tmp_diff);
- mch_remove(tmp_new);
- }
- mch_remove(tmp_orig);
+ vim_memset(&param, 0, sizeof(param));
+ vim_memset(&emit_cfg, 0, sizeof(emit_cfg));
+ vim_memset(&emit_cb, 0, sizeof(emit_cb));
- /* force updating cursor position on screen */
- curwin->w_valid_cursor.lnum = 0;
+ param.flags = diff_algorithm;
- diff_redraw(TRUE);
+ if (diff_flags & DIFF_IWHITE)
+ param.flags |= XDF_IGNORE_WHITESPACE_CHANGE;
-theend:
- vim_free(tmp_orig);
- vim_free(tmp_new);
- vim_free(tmp_diff);
+ emit_cfg.ctxlen = 0; // don't need any diff_context here
+ emit_cb.priv = &diffio->dio_diff;
+ emit_cb.outf = xdiff_out;
+ if (xdl_diff(&diffio->dio_orig.din_mmfile,
+ &diffio->dio_new.din_mmfile,
+ &param, &emit_cfg, &emit_cb) < 0)
+ {
+ EMSG(_("E960: Problem creating the internal diff"));
+ return FAIL;
+ }
+ return OK;
}
/*
* Make a diff between files "tmp_orig" and "tmp_new", results in "tmp_diff".
+ * return OK or FAIL;
*/
- static void
-diff_file(
- char_u *tmp_orig,
- char_u *tmp_new,
- char_u *tmp_diff)
+ static int
+diff_file(diffio_T *dio)
{
char_u *cmd;
size_t len;
+ char_u *tmp_orig = dio->dio_orig.din_fname;
+ char_u *tmp_new = dio->dio_new.din_fname;
+ char_u *tmp_diff = dio->dio_diff.dout_fname;
#ifdef FEAT_EVAL
if (*p_dex != NUL)
- /* Use 'diffexpr' to generate the diff file. */
+ {
+ // Use 'diffexpr' to generate the diff file.
eval_diff(tmp_orig, tmp_new, tmp_diff);
+ return OK;
+ }
else
#endif
+ // Use xdiff for generating the diff.
+ if (dio->dio_internal)
+ {
+ return diff_file_internal(dio);
+ }
+ else
{
len = STRLEN(tmp_orig) + STRLEN(tmp_new)
+ STRLEN(tmp_diff) + STRLEN(p_srr) + 27;
cmd = alloc((unsigned)len);
- if (cmd != NULL)
- {
- /* We don't want $DIFF_OPTIONS to get in the way. */
- if (getenv("DIFF_OPTIONS"))
- vim_setenv((char_u *)"DIFF_OPTIONS", (char_u *)"");
-
- /* Build the diff command and execute it. Always use -a, binary
- * differences are of no use. Ignore errors, diff returns
- * non-zero when differences have been found. */
- vim_snprintf((char *)cmd, len, "diff %s%s%s%s%s %s",
- diff_a_works == FALSE ? "" : "-a ",
+ if (cmd == NULL)
+ return FAIL;
+
+ // We don't want $DIFF_OPTIONS to get in the way.
+ if (getenv("DIFF_OPTIONS"))
+ vim_setenv((char_u *)"DIFF_OPTIONS", (char_u *)"");
+
+ // Build the diff command and execute it. Always use -a, binary
+ // differences are of no use. Ignore errors, diff returns
+ // non-zero when differences have been found.
+ vim_snprintf((char *)cmd, len, "diff %s%s%s%s%s %s",
+ diff_a_works == FALSE ? "" : "-a ",
#if defined(MSWIN)
- diff_bin_works == TRUE ? "--binary " : "",
+ diff_bin_works == TRUE ? "--binary " : "",
#else
- "",
+ "",
#endif
- (diff_flags & DIFF_IWHITE) ? "-b " : "",
- (diff_flags & DIFF_ICASE) ? "-i " : "",
- tmp_orig, tmp_new);
- append_redir(cmd, (int)len, p_srr, tmp_diff);
- block_autocmds(); /* Avoid ShellCmdPost stuff */
- (void)call_shell(cmd, SHELL_FILTER|SHELL_SILENT|SHELL_DOOUT);
- unblock_autocmds();
- vim_free(cmd);
- }
+ (diff_flags & DIFF_IWHITE) ? "-b " : "",
+ (diff_flags & DIFF_ICASE) ? "-i " : "",
+ tmp_orig, tmp_new);
+ append_redir(cmd, (int)len, p_srr, tmp_diff);
+ block_autocmds(); // avoid ShellCmdPost stuff
+ (void)call_shell(cmd, SHELL_FILTER|SHELL_SILENT|SHELL_DOOUT);
+ unblock_autocmds();
+ vim_free(cmd);
+ return OK;
}
}
@@ -1282,89 +1527,105 @@ ex_diffoff(exarg_T *eap)
*/
static void
diff_read(
- int idx_orig, /* idx of original file */
- int idx_new, /* idx of new file */
- char_u *fname) /* name of diff output file */
+ int idx_orig, // idx of original file
+ int idx_new, // idx of new file
+ diffout_T *dout) // diff output
{
- FILE *fd;
+ FILE *fd = NULL;
+ int line_idx = 0;
diff_T *dprev = NULL;
diff_T *dp = curtab->tp_first_diff;
diff_T *dn, *dpl;
- long f1, l1, f2, l2;
char_u linebuf[LBUFLEN]; /* only need to hold the diff line */
- int difftype;
- char_u *p;
+ char_u *line;
long off;
int i;
linenr_T lnum_orig, lnum_new;
long count_orig, count_new;
int notset = TRUE; /* block "*dp" not set yet */
+ enum {
+ DIFF_ED,
+ DIFF_UNIFIED,
+ DIFF_NONE
+ } diffstyle = DIFF_NONE;
- fd = mch_fopen((char *)fname, "r");
- if (fd == NULL)
+ if (dout->dout_fname == NULL)
{
- EMSG(_("E98: Cannot read diff output"));
- return;
+ diffstyle = DIFF_UNIFIED;
+ }
+ else
+ {
+ fd = mch_fopen((char *)dout->dout_fname, "r");
+ if (fd == NULL)
+ {
+ EMSG(_("E98: Cannot read diff output"));
+ return;
+ }
}
for (;;)
{
- if (tag_fgets(linebuf, LBUFLEN, fd))
- break; /* end of file */
- if (!isdigit(*linebuf))
- continue; /* not the start of a diff block */
-
- /* This line must be one of three formats:
- * {first}[,{last}]c{first}[,{last}]
- * {first}a{first}[,{last}]
- * {first}[,{last}]d{first}
- */
- p = linebuf;
- f1 = getdigits(&p);
- if (*p == ',')
+ if (fd == NULL)
{
- ++p;
- l1 = getdigits(&p);
+ if (line_idx >= dout->dout_ga.ga_len)
+ break; // did last line
+ line = ((char_u **)dout->dout_ga.ga_data)[line_idx++];
}
else
- l1 = f1;
- if (*p != 'a' && *p != 'c' && *p != 'd')
- continue; /* invalid diff format */
- difftype = *p++;
- f2 = getdigits(&p);
- if (*p == ',')
{
- ++p;
- l2 = getdigits(&p);
+ if (tag_fgets(linebuf, LBUFLEN, fd))
+ break; // end of file
+ line = linebuf;
}
- else
- l2 = f2;
- if (l1 < f1 || l2 < f2)
- continue; /* invalid line range */
- if (difftype == 'a')
+ if (diffstyle == DIFF_NONE)
{
- lnum_orig = f1 + 1;
- count_orig = 0;
+ // Determine diff style.
+ // ed like diff looks like this:
+ // {first}[,{last}]c{first}[,{last}]
+ // {first}a{first}[,{last}]
+ // {first}[,{last}]d{first}
+ //
+ // unified diff looks like this:
+ // --- file1 2018-03-20 13:23:35.783153140 +0100
+ // +++ file2 2018-03-20 13:23:41.183156066 +0100
+ // @@ -1,3 +1,5 @@
+ if (isdigit(*line))
+ diffstyle = DIFF_ED;
+ else if ((STRNCMP(line, "@@ ", 3) == 0))
+ diffstyle = DIFF_UNIFIED;
+ else if ((STRNCMP(line, "--- ", 4) == 0)
+ && (tag_fgets(linebuf, LBUFLEN, fd) == 0)
+ && (STRNCMP(line, "+++ ", 4) == 0)
+ && (tag_fgets(linebuf, LBUFLEN, fd) == 0)
+ && (STRNCMP(line, "@@ ", 3) == 0))
+ diffstyle = DIFF_UNIFIED;
}
- else
+
+ if (diffstyle == DIFF_ED)
{
- lnum_orig = f1;
- count_orig = l1 - f1 + 1;
+ if (!isdigit(*line))
+ continue; // not the start of a diff block
+ if (parse_diff_ed(line, &lnum_orig, &count_orig,
+ &lnum_new, &count_new) == FAIL)
+ continue;
}
- if (difftype == 'd')
+ else if (diffstyle == DIFF_UNIFIED)
{
- lnum_new = f2 + 1;
- count_new = 0;
+ if (STRNCMP(line, "@@ ", 3) != 0)
+ continue; // not the start of a diff block
+ if (parse_diff_unified(line, &lnum_orig, &count_orig,
+ &lnum_new, &count_new) == FAIL)
+ continue;
}
else
{
- lnum_new = f2;
- count_new = l2 - f2 + 1;
+ EMSG(_("E959: Invalid diff format."));
+ break;
}
- /* Go over blocks before the change, for which orig and new are equal.
- * Copy blocks from orig to new. */
+ // Go over blocks before the change, for which orig and new are equal.
+ // Copy blocks from orig to new.
while (dp != NULL
&& lnum_orig > dp->df_lnum[idx_orig] + dp->df_count[idx_orig])
{
@@ -1379,14 +1640,14 @@ diff_read(
&& lnum_orig <= dp->df_lnum[idx_orig] + dp->df_count[idx_orig]
&& lnum_orig + count_orig >= dp->df_lnum[idx_orig])
{
- /* New block overlaps with existing block(s).
- * First find last block that overlaps. */
+ // New block overlaps with existing block(s).
+ // First find last block that overlaps.
for (dpl = dp; dpl->df_next != NULL; dpl = dpl->df_next)
if (lnum_orig + count_orig < dpl->df_next->df_lnum[idx_orig])
break;
- /* If the newly found block starts before the old one, set the
- * start back a number of lines. */
+ // If the newly found block starts before the old one, set the
+ // start back a number of lines.
off = dp->df_lnum[idx_orig] - lnum_orig;
if (off > 0)
{
@@ -1398,24 +1659,24 @@ diff_read(
}
else if (notset)
{
- /* new block inside existing one, adjust new block */
+ // new block inside existing one, adjust new block
dp->df_lnum[idx_new] = lnum_new + off;
dp->df_count[idx_new] = count_new - off;
}
else
- /* second overlap of new block with existing block */
+ // second overlap of new block with existing block
dp->df_count[idx_new] += count_new - count_orig
+ dpl->df_lnum[idx_orig] + dpl->df_count[idx_orig]
- (dp->df_lnum[idx_orig] + dp->df_count[idx_orig]);
- /* Adjust the size of the block to include all the lines to the
- * end of the existing block or the new diff, whatever ends last. */
+ // Adjust the size of the block to include all the lines to the
+ // end of the existing block or the new diff, whatever ends last.
off = (lnum_orig + count_orig)
- (dpl->df_lnum[idx_orig] + dpl->df_count[idx_orig]);
if (off < 0)
{
- /* new change ends in existing block, adjust the end if not
- * done already */
+ // new change ends in existing block, adjust the end if not
+ // done already
if (notset)
dp->df_count[idx_new] += -off;
off = 0;
@@ -1425,7 +1686,7 @@ diff_read(
dp->df_count[i] = dpl->df_lnum[i] + dpl->df_count[i]
- dp->df_lnum[i] + off;
- /* Delete the diff blocks that have been merged into one. */
+ // Delete the diff blocks that have been merged into one.
dn = dp->df_next;
dp->df_next = dpl->df_next;
while (dn != dp->df_next)
@@ -1437,7 +1698,7 @@ diff_read(
}
else
{
- /* Allocate a new diffblock. */
+ // Allocate a new diffblock.
dp = diff_alloc_new(curtab, dprev, dp);
if (dp == NULL)
goto done;
@@ -1447,17 +1708,17 @@ diff_read(
dp->df_lnum[idx_new] = lnum_new;
dp->df_count[idx_new] = count_new;
- /* Set values for other buffers, these must be equal to the
- * original buffer, otherwise there would have been a change
- * already. */
+ // Set values for other buffers, these must be equal to the
+ // original buffer, otherwise there would have been a change
+ // already.
for (i = idx_orig + 1; i < idx_new; ++i)
if (curtab->tp_diffbuf[i] != NULL)
diff_copy_entry(dprev, dp, idx_orig, i);
}
- notset = FALSE; /* "*dp" has been set */
+ notset = FALSE; // "*dp" has been set
}
- /* for remaining diff blocks orig and new are equal */
+ // for remaining diff blocks orig and new are equal
while (dp != NULL)
{
if (notset)
@@ -1468,7 +1729,8 @@ diff_read(
}
done:
- fclose(fd);
+ if (fd != NULL)
+ fclose(fd);
}
/*
@@ -1860,6 +2122,7 @@ diffopt_changed(void)
int diff_context_new = 6;
int diff_flags_new = 0;
int diff_foldcolumn_new = 2;
+ long diff_algorithm_new = 0;
tabpage_T *tp;
p = p_dip;
@@ -1905,6 +2168,41 @@ diffopt_changed(void)
p += 9;
diff_flags_new |= DIFF_HIDDEN_OFF;
}
+ else if (STRNCMP(p, "indent-heuristic", 16) == 0)
+ {
+ p += 16;
+ diff_algorithm_new |= XDF_INDENT_HEURISTIC;
+ }
+ else if (STRNCMP(p, "internal", 8) == 0)
+ {
+ p += 8;
+ diff_flags_new |= DIFF_INTERNAL;
+ }
+ else if (STRNCMP(p, "algorithm:", 10) == 0)
+ {
+ p += 10;
+ if (STRNCMP(p, "myers", 5) == 0)
+ {
+ p += 5;
+ diff_algorithm_new = 0;
+ }
+ else if (STRNCMP(p, "minimal", 7) == 0)
+ {
+ p += 7;
+ diff_algorithm_new = XDF_NEED_MINIMAL;
+ }
+ else if (STRNCMP(p, "patience", 8) == 0)
+ {
+ p += 8;
+ diff_algorithm_new = XDF_PATIENCE_DIFF;
+ }
+ else if (STRNCMP(p, "histogram", 9) == 0)
+ {
+ p += 9;
+ diff_algorithm_new = XDF_HISTOGRAM_DIFF;
+ }
+ }
+
if (*p != ',' && *p != NUL)
return FAIL;
if (*p == ',')
@@ -1916,13 +2214,14 @@ diffopt_changed(void)
return FAIL;
/* If "icase" or "iwhite" was added or removed, need to update the diff. */
- if (diff_flags != diff_flags_new)
+ if (diff_flags != diff_flags_new || diff_algorithm != diff_algorithm_new)
FOR_ALL_TABPAGES(tp)
tp->tp_diff_invalid = TRUE;
diff_flags = diff_flags_new;
diff_context = diff_context_new;
diff_foldcolumn = diff_foldcolumn_new;
+ diff_algorithm = diff_algorithm_new;
diff_redraw(TRUE);
@@ -2690,4 +2989,156 @@ diff_lnum_win(linenr_T lnum, win_T *wp)
return n;
}
+/*
+ * Handle an ED style diff line.
+ * Return FAIL if the line does not contain diff info.
+ */
+ static int
+parse_diff_ed(
+ char_u *line,
+ linenr_T *lnum_orig,
+ long *count_orig,
+ linenr_T *lnum_new,
+ long *count_new)
+{
+ char_u *p;
+ long f1, l1, f2, l2;
+ int difftype;
+
+ // The line must be one of three formats:
+ // change: {first}[,{last}]c{first}[,{last}]
+ // append: {first}a{first}[,{last}]
+ // delete: {first}[,{last}]d{first}
+ p = line;
+ f1 = getdigits(&p);
+ if (*p == ',')
+ {
+ ++p;
+ l1 = getdigits(&p);
+ }
+ else
+ l1 = f1;
+ if (*p != 'a' && *p != 'c' && *p != 'd')
+ return FAIL; // invalid diff format
+ difftype = *p++;
+ f2 = getdigits(&p);
+ if (*p == ',')
+ {
+ ++p;
+ l2 = getdigits(&p);
+ }
+ else
+ l2 = f2;
+ if (l1 < f1 || l2 < f2)
+ return FAIL;
+
+ if (difftype == 'a')
+ {
+ *lnum_orig = f1 + 1;
+ *count_orig = 0;
+ }
+ else
+ {
+ *lnum_orig = f1;
+ *count_orig = l1 - f1 + 1;
+ }
+ if (difftype == 'd')
+ {
+ *lnum_new = f2 + 1;
+ *count_new = 0;
+ }
+ else
+ {
+ *lnum_new = f2;
+ *count_new = l2 - f2 + 1;
+ }
+ return OK;
+}
+
+/*
+ * Parses unified diff with zero(!) context lines.
+ * Return FAIL if there is no diff information in "line".
+ */
+ static int
+parse_diff_unified(
+ char_u *line,
+ linenr_T *lnum_orig,
+ long *count_orig,
+ linenr_T *lnum_new,
+ long *count_new)
+{
+ char_u *p;
+ long oldline, oldcount, newline, newcount;
+
+ // Parse unified diff hunk header:
+ // @@ -oldline,oldcount +newline,newcount @@
+ p = line;
+ if (*p++ == '@' && *p++ == '@' && *p++ == ' ' && *p++ == '-')
+ {
+ oldline = getdigits(&p);
+ if (*p == ',')
+ {
+ ++p;
+ oldcount = getdigits(&p);
+ }
+ else
+ oldcount = 1;
+ if (*p++ == ' ' && *p++ == '+')
+ {
+ newline = getdigits(&p);
+ if (*p == ',')
+ {
+ ++p;
+ newcount = getdigits(&p);
+ }
+ else
+ newcount = 1;
+ }
+ else
+ return FAIL; // invalid diff format
+
+ if (oldcount == 0)
+ oldline += 1;
+ if (newcount == 0)
+ newline += 1;
+ if (newline == 0)
+ newline = 1;
+
+ *lnum_orig = oldline;
+ *count_orig = oldcount;
+ *lnum_new = newline;
+ *count_new = newcount;
+
+ return OK;
+ }
+
+ return FAIL;
+}
+
+/*
+ * Callback function for the xdl_diff() function.
+ * Stores the diff output in a grow array.
+ */
+ static int
+xdiff_out(void *priv, mmbuffer_t *mb, int nbuf)
+{
+ diffout_T *dout = (diffout_T *)priv;
+ int i;
+ char_u *p;
+
+ for (i = 0; i < nbuf; i++)
+ {
+ // We are only interested in the header lines, skip text lines.
+ if (STRNCMP(mb[i].ptr, "@@ ", 3) != 0)
+ continue;
+ if (ga_grow(&dout->dout_ga, 1) == FAIL)
+ return -1;
+ p = vim_strnsave((char_u *)mb[i].ptr, mb[i].size);
+ if (p == NULL)
+ return -1;
+ ((char_u **)dout->dout_ga.ga_data)[dout->dout_ga.ga_len++] = p;
+ }
+ return 0;
+}
+
#endif /* FEAT_DIFF */