diff options
author | Bram Moolenaar <Bram@vim.org> | 2018-09-10 17:51:58 +0200 |
---|---|---|
committer | Bram Moolenaar <Bram@vim.org> | 2018-09-10 17:51:58 +0200 |
commit | e828b7621cf9065a3582be0c4dd1e0e846e335bf (patch) | |
tree | 79cf05b6295837108fb6edbbc154e333c940698a /src/diff.c | |
parent | 93a1df2c205c8399d96c172d9483e0793d32892a (diff) | |
download | vim-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.c | 813 |
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(¶m, 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, + ¶m, &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 */ |