2458 lines · 59.1 KB · C
RawDownload
/* vim:set ts=8 sts=4 sw=4: * * VIM - Vi IMproved	by Bram Moolenaar * * Do ":help uganda"  in Vim to read copying and usage conditions. * Do ":help credits" in Vim to see a list of people who contributed. * See README.txt for an overview of the Vim source code. */ /* * diff.c: code for diff'ing two or three buffers. */ #include "vim.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 */static int	diff_flags = DIFF_FILLER; #define LBUFLEN 50		/* length of line in diff file */ static int diff_a_works = MAYBE; /* TRUE when "diff -a" works, FALSE when it				    doesn't work, MAYBE when not checked yet */#if defined(MSWIN) || defined(MSDOS)static int diff_bin_works = MAYBE; /* TRUE when "diff --binary" works, FALSE				      when it doesn't work, MAYBE when not				      checked yet */#endif static int diff_buf_idx __ARGS((buf_T *buf));static int diff_buf_idx_tp __ARGS((buf_T *buf, tabpage_T *tp));static void diff_mark_adjust_tp __ARGS((tabpage_T *tp, int idx, linenr_T line1, linenr_T line2, long amount, long amount_after));static void diff_check_unchanged __ARGS((tabpage_T *tp, diff_T *dp));static int diff_check_sanity __ARGS((tabpage_T *tp, diff_T *dp));static void diff_redraw __ARGS((int dofold));static int diff_write __ARGS((buf_T *buf, char_u *fname));static void diff_file __ARGS((char_u *tmp_orig, char_u *tmp_new, char_u *tmp_diff));static int diff_equal_entry __ARGS((diff_T *dp, int idx1, int idx2));static int diff_cmp __ARGS((char_u *s1, char_u *s2));#ifdef FEAT_FOLDINGstatic void diff_fold_update __ARGS((diff_T *dp, int skip_idx));#endifstatic void diff_read __ARGS((int idx_orig, int idx_new, char_u *fname));static void diff_copy_entry __ARGS((diff_T *dprev, diff_T *dp, int idx_orig, int idx_new));static diff_T *diff_alloc_new __ARGS((tabpage_T *tp, diff_T *dprev, diff_T *dp)); #ifndef USE_CR# define tag_fgets vim_fgets#endif /* * Called when deleting or unloading a buffer: No longer make a diff with it. */    voiddiff_buf_delete(buf)    buf_T	*buf;{    int		i;    tabpage_T	*tp;     for (tp = first_tabpage; tp != NULL; tp = tp->tp_next)    {	i = diff_buf_idx_tp(buf, tp);	if (i != DB_COUNT)	{	    tp->tp_diffbuf[i] = NULL;	    tp->tp_diff_invalid = TRUE;	}    }} /* * Check if the current buffer should be added to or removed from the list of * diff buffers. */    voiddiff_buf_adjust(win)    win_T	*win;{    win_T	*wp;    int		i;     if (!win->w_p_diff)    {	/* When there is no window showing a diff for this buffer, remove	 * it from the diffs. */	for (wp = firstwin; wp != NULL; wp = wp->w_next)	    if (wp->w_buffer == win->w_buffer && wp->w_p_diff)		break;	if (wp == NULL)	{	    i = diff_buf_idx(win->w_buffer);	    if (i != DB_COUNT)	    {		curtab->tp_diffbuf[i] = NULL;		curtab->tp_diff_invalid = TRUE;	    }	}    }    else	diff_buf_add(win->w_buffer);} /* * Add a buffer to make diffs for. * Call this when a new buffer is being edited in the current window where * 'diff' is set. * Marks the current buffer as being part of the diff and requireing updating. * This must be done before any autocmd, because a command may use info * about the screen contents. */    voiddiff_buf_add(buf)    buf_T	*buf;{    int		i;     if (diff_buf_idx(buf) != DB_COUNT)	return;		/* It's already there. */     for (i = 0; i < DB_COUNT; ++i)	if (curtab->tp_diffbuf[i] == NULL)	{	    curtab->tp_diffbuf[i] = buf;	    curtab->tp_diff_invalid = TRUE;	    return;	}     EMSGN(_("E96: Can not diff more than %ld buffers"), DB_COUNT);} /* * Find buffer "buf" in the list of diff buffers for the current tab page. * Return its index or DB_COUNT if not found. */    static intdiff_buf_idx(buf)    buf_T	*buf;{    int		idx;     for (idx = 0; idx < DB_COUNT; ++idx)	if (curtab->tp_diffbuf[idx] == buf)	    break;    return idx;} /* * Find buffer "buf" in the list of diff buffers for tab page "tp". * Return its index or DB_COUNT if not found. */    static intdiff_buf_idx_tp(buf, tp)    buf_T	*buf;    tabpage_T	*tp;{    int		idx;     for (idx = 0; idx < DB_COUNT; ++idx)	if (tp->tp_diffbuf[idx] == buf)	    break;    return idx;} /* * Mark the diff info involving buffer "buf" as invalid, it will be updated * when info is requested. */    voiddiff_invalidate(buf)    buf_T	*buf;{    tabpage_T	*tp;    int		i;     for (tp = first_tabpage; tp != NULL; tp = tp->tp_next)    {	i = diff_buf_idx_tp(buf, tp);	if (i != DB_COUNT)	{	    tp->tp_diff_invalid = TRUE;	    if (tp == curtab)		diff_redraw(TRUE);	}    }} /* * Called by mark_adjust(): update line numbers in "curbuf". */    voiddiff_mark_adjust(line1, line2, amount, amount_after)    linenr_T	line1;    linenr_T	line2;    long	amount;    long	amount_after;{    int		idx;    tabpage_T	*tp;     /* Handle all tab pages that use the current buffer in a diff. */    for (tp = first_tabpage; tp != NULL; tp = tp->tp_next)    {	idx = diff_buf_idx_tp(curbuf, tp);	if (idx != DB_COUNT)	    diff_mark_adjust_tp(tp, idx, line1, line2, amount, amount_after);    }} /* * Update line numbers in tab page "tp" for "curbuf" with index "idx". * This attempts to update the changes as much as possible: * When inserting/deleting lines outside of existing change blocks, create a * new change block and update the line numbers in following blocks. * When inserting/deleting lines in existing change blocks, update them. */    static voiddiff_mark_adjust_tp(tp, idx, line1, line2, amount, amount_after)    tabpage_T	*tp;    int		idx;    linenr_T	line1;    linenr_T	line2;    long	amount;    long	amount_after;{    diff_T	*dp;    diff_T	*dprev;    diff_T	*dnext;    int		i;    int		inserted, deleted;    int		n, off;    linenr_T	last;    linenr_T	lnum_deleted = line1;	/* lnum of remaining deletion */    int		check_unchanged;     if (line2 == MAXLNUM)    {	/* mark_adjust(99, MAXLNUM, 9, 0): insert lines */	inserted = amount;	deleted = 0;    }    else if (amount_after > 0)    {	/* mark_adjust(99, 98, MAXLNUM, 9): a change that inserts lines*/	inserted = amount_after;	deleted = 0;    }    else    {	/* mark_adjust(98, 99, MAXLNUM, -2): delete lines */	inserted = 0;	deleted = -amount_after;    }     dprev = NULL;    dp = tp->tp_first_diff;    for (;;)    {	/* If the change is after the previous diff block and before the next	 * diff block, thus not touching an existing change, create a new diff	 * block.  Don't do this when ex_diffgetput() is busy. */	if ((dp == NULL || dp->df_lnum[idx] - 1 > line2		    || (line2 == MAXLNUM && dp->df_lnum[idx] > line1))		&& (dprev == NULL		    || dprev->df_lnum[idx] + dprev->df_count[idx] < line1)		&& !diff_busy)	{	    dnext = diff_alloc_new(tp, dprev, dp);	    if (dnext == NULL)		return; 	    dnext->df_lnum[idx] = line1;	    dnext->df_count[idx] = inserted;	    for (i = 0; i < DB_COUNT; ++i)		if (tp->tp_diffbuf[i] != NULL && i != idx)		{		    if (dprev == NULL)			dnext->df_lnum[i] = line1;		    else			dnext->df_lnum[i] = line1			    + (dprev->df_lnum[i] + dprev->df_count[i])			    - (dprev->df_lnum[idx] + dprev->df_count[idx]);		    dnext->df_count[i] = deleted;		}	} 	/* if at end of the list, quit */	if (dp == NULL)	    break; 	/*	 * Check for these situations:	 *	  1  2	3	 *	  1  2	3	 * line1     2	3  4  5	 *	     2	3  4  5	 *	     2	3  4  5	 * line2     2	3  4  5	 *		3     5  6	 *		3     5  6	 */	/* compute last line of this change */	last = dp->df_lnum[idx] + dp->df_count[idx] - 1; 	/* 1. change completely above line1: nothing to do */	if (last >= line1 - 1)	{	    /* 6. change below line2: only adjust for amount_after; also when	     * "deleted" became zero when deleted all lines between two diffs */	    if (dp->df_lnum[idx] - (deleted + inserted != 0) > line2)	    {		if (amount_after == 0)		    break;	/* nothing left to change */		dp->df_lnum[idx] += amount_after;	    }	    else	    {		check_unchanged = FALSE; 		/* 2. 3. 4. 5.: inserted/deleted lines touching this diff. */		if (deleted > 0)		{		    if (dp->df_lnum[idx] >= line1)		    {			off = dp->df_lnum[idx] - lnum_deleted;			if (last <= line2)			{			    /* 4. delete all lines of diff */			    if (dp->df_next != NULL				    && dp->df_next->df_lnum[idx] - 1 <= line2)			    {				/* delete continues in next diff, only do				 * lines until that one */				n = dp->df_next->df_lnum[idx] - lnum_deleted;				deleted -= n;				n -= dp->df_count[idx];				lnum_deleted = dp->df_next->df_lnum[idx];			    }			    else				n = deleted - dp->df_count[idx];			    dp->df_count[idx] = 0;			}			else			{			    /* 5. delete lines at or just before top of diff */			    n = off;			    dp->df_count[idx] -= line2 - dp->df_lnum[idx] + 1;			    check_unchanged = TRUE;			}			dp->df_lnum[idx] = line1;		    }		    else		    {			off = 0;			if (last < line2)			{			    /* 2. delete at end of of diff */			    dp->df_count[idx] -= last - lnum_deleted + 1;			    if (dp->df_next != NULL				    && dp->df_next->df_lnum[idx] - 1 <= line2)			    {				/* delete continues in next diff, only do				 * lines until that one */				n = dp->df_next->df_lnum[idx] - 1 - last;				deleted -= dp->df_next->df_lnum[idx]							       - lnum_deleted;				lnum_deleted = dp->df_next->df_lnum[idx];			    }			    else				n = line2 - last;			    check_unchanged = TRUE;			}			else			{			    /* 3. delete lines inside the diff */			    n = 0;			    dp->df_count[idx] -= deleted;			}		    } 		    for (i = 0; i < DB_COUNT; ++i)			if (tp->tp_diffbuf[i] != NULL && i != idx)			{			    dp->df_lnum[i] -= off;			    dp->df_count[i] += n;			}		}		else		{		    if (dp->df_lnum[idx] <= line1)		    {			/* inserted lines somewhere in this diff */			dp->df_count[idx] += inserted;			check_unchanged = TRUE;		    }		    else			/* inserted lines somewhere above this diff */			dp->df_lnum[idx] += inserted;		} 		if (check_unchanged)		    /* Check if inserted lines are equal, may reduce the		     * size of the diff.  TODO: also check for equal lines		     * in the middle and perhaps split the block. */		    diff_check_unchanged(tp, dp);	    }	} 	/* check if this block touches the previous one, may merge them. */	if (dprev != NULL && dprev->df_lnum[idx] + dprev->df_count[idx]							  == dp->df_lnum[idx])	{	    for (i = 0; i < DB_COUNT; ++i)		if (tp->tp_diffbuf[i] != NULL)		    dprev->df_count[i] += dp->df_count[i];	    dprev->df_next = dp->df_next;	    vim_free(dp);	    dp = dprev->df_next;	}	else	{	    /* Advance to next entry. */	    dprev = dp;	    dp = dp->df_next;	}    }     dprev = NULL;    dp = tp->tp_first_diff;    while (dp != NULL)    {	/* All counts are zero, remove this entry. */	for (i = 0; i < DB_COUNT; ++i)	    if (tp->tp_diffbuf[i] != NULL && dp->df_count[i] != 0)		break;	if (i == DB_COUNT)	{	    dnext = dp->df_next;	    vim_free(dp);	    dp = dnext;	    if (dprev == NULL)		tp->tp_first_diff = dnext;	    else		dprev->df_next = dnext;	}	else	{	    /* Advance to next entry. */	    dprev = dp;	    dp = dp->df_next;	}     }     if (tp == curtab)    {	diff_redraw(TRUE); 	/* Need to recompute the scroll binding, may remove or add filler	 * lines (e.g., when adding lines above w_topline). But it's slow when	 * making many changes, postpone until redrawing. */	diff_need_scrollbind = TRUE;    }} /* * Allocate a new diff block and link it between "dprev" and "dp". */    static diff_T *diff_alloc_new(tp, dprev, dp)    tabpage_T	*tp;    diff_T	*dprev;    diff_T	*dp;{    diff_T	*dnew;     dnew = (diff_T *)alloc((unsigned)sizeof(diff_T));    if (dnew != NULL)    {	dnew->df_next = dp;	if (dprev == NULL)	    tp->tp_first_diff = dnew;	else	    dprev->df_next = dnew;    }    return dnew;} /* * Check if the diff block "dp" can be made smaller for lines at the start and * end that are equal.  Called after inserting lines. * This may result in a change where all buffers have zero lines, the caller

Lines 1–500 of 2458.