2150 lines · 54.6 KB · C
RawDownload
/* vi: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. */ /* * undo.c: multi level undo facility * * The saved lines are stored in a list of lists (one for each buffer): * * b_u_oldhead------------------------------------------------+ *							      | *							      V *		  +--------------+    +--------------+	  +--------------+ * b_u_newhead--->| u_header	 |    | u_header     |	  | u_header	 | *		  |	uh_next------>|     uh_next------>|	uh_next---->NULL *	   NULL<--------uh_prev  |<---------uh_prev  |<---------uh_prev  | *		  |	uh_entry |    |     uh_entry |	  |	uh_entry | *		  +--------|-----+    +--------|-----+	  +--------|-----+ *			   |		       |		   | *			   V		       V		   V *		  +--------------+    +--------------+	  +--------------+ *		  | u_entry	 |    | u_entry      |	  | u_entry	 | *		  |	ue_next  |    |     ue_next  |	  |	ue_next  | *		  +--------|-----+    +--------|-----+	  +--------|-----+ *			   |		       |		   | *			   V		       V		   V *		  +--------------+	      NULL		  NULL *		  | u_entry	 | *		  |	ue_next  | *		  +--------|-----+ *			   | *			   V *			  etc. * * Each u_entry list contains the information for one undo or redo. * curbuf->b_u_curhead points to the header of the last undo (the next redo), * or is NULL if nothing has been undone (end of the branch). * * For keeping alternate undo/redo branches the uh_alt field is used.  Thus at * each point in the list a branch may appear for an alternate to redo.  The * uh_seq field is numbered sequentially to be able to find a newer or older * branch. * *		   +---------------+	+---------------+ * b_u_oldhead --->| u_header	   |	| u_header	| *		   |   uh_alt_next ---->|   uh_alt_next ----> NULL *	   NULL <----- uh_alt_prev |<------ uh_alt_prev | *		   |   uh_prev	   |	|   uh_prev	| *		   +-----|---------+	+-----|---------+ *			 |		      | *			 V		      V *		   +---------------+	+---------------+ *		   | u_header	   |	| u_header	| *		   |   uh_alt_next |	|   uh_alt_next | * b_u_newhead --->|   uh_alt_prev |	|   uh_alt_prev | *		   |   uh_prev	   |	|   uh_prev	| *		   +-----|---------+	+-----|---------+ *			 |		      | *			 V		      V *		       NULL		+---------------+    +---------------+ *					| u_header	|    | u_header      | *					|   uh_alt_next ---->|	 uh_alt_next | *					|   uh_alt_prev |<------ uh_alt_prev | *					|   uh_prev	|    |	 uh_prev     | *					+-----|---------+    +-----|---------+ *					      |			   | *					     etc.		  etc. * * * All data is allocated with U_ALLOC_LINE(), it will be freed as soon as the * buffer is unloaded. */ #include "vim.h" /* See below: use malloc()/free() for memory management. */#define U_USE_MALLOC 1 static void u_unch_branch __ARGS((u_header_T *uhp));static u_entry_T *u_get_headentry __ARGS((void));static void u_getbot __ARGS((void));static int u_savecommon __ARGS((linenr_T, linenr_T, linenr_T));static void u_doit __ARGS((int count));static void u_undoredo __ARGS((int undo));static void u_undo_end __ARGS((int did_undo, int absolute));static void u_add_time __ARGS((char_u *buf, size_t buflen, time_t tt));static void u_freeheader __ARGS((buf_T *buf, u_header_T *uhp, u_header_T **uhpp));static void u_freebranch __ARGS((buf_T *buf, u_header_T *uhp, u_header_T **uhpp));static void u_freeentries __ARGS((buf_T *buf, u_header_T *uhp, u_header_T **uhpp));static void u_freeentry __ARGS((u_entry_T *, long)); #ifdef U_USE_MALLOC# define U_FREE_LINE(ptr) vim_free(ptr)# define U_ALLOC_LINE(size) lalloc((long_u)((size) + 1), FALSE)#elsestatic void u_free_line __ARGS((char_u *ptr, int keep));static char_u *u_alloc_line __ARGS((unsigned size));# define U_FREE_LINE(ptr) u_free_line((ptr), FALSE)# define U_ALLOC_LINE(size) u_alloc_line(size)#endifstatic char_u *u_save_line __ARGS((linenr_T)); static long	u_newcount, u_oldcount; /* * When 'u' flag included in 'cpoptions', we behave like vi.  Need to remember * the action that "u" should do. */static int	undo_undoes = FALSE; /* * Save the current line for both the "u" and "U" command. * Returns OK or FAIL. */    intu_save_cursor(){    return (u_save((linenr_T)(curwin->w_cursor.lnum - 1),				      (linenr_T)(curwin->w_cursor.lnum + 1)));} /* * Save the lines between "top" and "bot" for both the "u" and "U" command. * "top" may be 0 and bot may be curbuf->b_ml.ml_line_count + 1. * Returns FAIL when lines could not be saved, OK otherwise. */    intu_save(top, bot)    linenr_T top, bot;{    if (undo_off)	return OK;     if (top > curbuf->b_ml.ml_line_count ||			    top >= bot || bot > curbuf->b_ml.ml_line_count + 1)	return FALSE;	/* rely on caller to do error messages */     if (top + 2 == bot)	u_saveline((linenr_T)(top + 1));     return (u_savecommon(top, bot, (linenr_T)0));} /* * save the line "lnum" (used by ":s" and "~" command) * The line is replaced, so the new bottom line is lnum + 1. */    intu_savesub(lnum)    linenr_T	lnum;{    if (undo_off)	return OK;     return (u_savecommon(lnum - 1, lnum + 1, lnum + 1));} /* * a new line is inserted before line "lnum" (used by :s command) * The line is inserted, so the new bottom line is lnum + 1. */    intu_inssub(lnum)    linenr_T	lnum;{    if (undo_off)	return OK;     return (u_savecommon(lnum - 1, lnum, lnum + 1));} /* * save the lines "lnum" - "lnum" + nlines (used by delete command) * The lines are deleted, so the new bottom line is lnum, unless the buffer * becomes empty. */    intu_savedel(lnum, nlines)    linenr_T	lnum;    long	nlines;{    if (undo_off)	return OK;     return (u_savecommon(lnum - 1, lnum + nlines,			nlines == curbuf->b_ml.ml_line_count ? 2 : lnum));} /* * Return TRUE when undo is allowed.  Otherwise give an error message and * return FALSE. */    intundo_allowed(){    /* Don't allow changes when 'modifiable' is off.  */    if (!curbuf->b_p_ma)    {	EMSG(_(e_modifiable));	return FALSE;    } #ifdef HAVE_SANDBOX    /* In the sandbox it's not allowed to change the text. */    if (sandbox != 0)    {	EMSG(_(e_sandbox));	return FALSE;    }#endif     /* Don't allow changes in the buffer while editing the cmdline.  The     * caller of getcmdline() may get confused. */    if (textlock != 0)    {	EMSG(_(e_secure));	return FALSE;    }     return TRUE;}     static intu_savecommon(top, bot, newbot)    linenr_T	top, bot;    linenr_T	newbot;{    linenr_T	lnum;    long	i;    u_header_T	*uhp;    u_header_T	*old_curhead;    u_entry_T	*uep;    u_entry_T	*prev_uep;    long	size;     /* When making changes is not allowed return FAIL.  It's a crude way to     * make all change commands fail. */    if (!undo_allowed())	return FAIL; #ifdef FEAT_NETBEANS_INTG    /*     * Netbeans defines areas that cannot be modified.  Bail out here when     * trying to change text in a guarded area.     */    if (usingNetbeans)    {	if (netbeans_is_guarded(top, bot))	{	    EMSG(_(e_guarded));	    return FAIL;	}	if (curbuf->b_p_ro)	{	    EMSG(_(e_nbreadonly));	    return FAIL;	}    }#endif #ifdef FEAT_AUTOCMD    /*     * Saving text for undo means we are going to make a change.  Give a     * warning for a read-only file before making the change, so that the     * FileChangedRO event can replace the buffer with a read-write version     * (e.g., obtained from a source control system).     */    change_warning(0);#endif     size = bot - top - 1;     /*     * if curbuf->b_u_synced == TRUE make a new header     */    if (curbuf->b_u_synced)    {#ifdef FEAT_JUMPLIST	/* Need to create new entry in b_changelist. */	curbuf->b_new_change = TRUE;#endif 	if (p_ul >= 0)	{	    /*	     * Make a new header entry.  Do this first so that we don't mess	     * up the undo info when out of memory.	     */	    uhp = (u_header_T *)U_ALLOC_LINE((unsigned)sizeof(u_header_T));	    if (uhp == NULL)		goto nomem;	}	else	    uhp = NULL; 	/*	 * If we undid more than we redid, move the entry lists before and	 * including curbuf->b_u_curhead to an alternate branch.	 */	old_curhead = curbuf->b_u_curhead;	if (old_curhead != NULL)	{	    curbuf->b_u_newhead = old_curhead->uh_next;	    curbuf->b_u_curhead = NULL;	} 	/*	 * free headers to keep the size right	 */	while (curbuf->b_u_numhead > p_ul && curbuf->b_u_oldhead != NULL)	{	    u_header_T	    *uhfree = curbuf->b_u_oldhead; 	    /* If there is no branch only free one header. */	    if (uhfree->uh_alt_next == NULL)		u_freeheader(curbuf, uhfree, &old_curhead);	    else	    {		/* Free the oldest alternate branch as a whole. */		while (uhfree->uh_alt_next != NULL)		    uhfree = uhfree->uh_alt_next;		u_freebranch(curbuf, uhfree, &old_curhead);	    }	} 	if (uhp == NULL)		/* no undo at all */	{	    if (old_curhead != NULL)		u_freebranch(curbuf, old_curhead, NULL);	    curbuf->b_u_synced = FALSE;	    return OK;	} 	uhp->uh_prev = NULL;	uhp->uh_next = curbuf->b_u_newhead;	uhp->uh_alt_next = old_curhead;	if (old_curhead != NULL)	{	    uhp->uh_alt_prev = old_curhead->uh_alt_prev;	    if (uhp->uh_alt_prev != NULL)		uhp->uh_alt_prev->uh_alt_next = uhp;	    old_curhead->uh_alt_prev = uhp;	    if (curbuf->b_u_oldhead == old_curhead)		curbuf->b_u_oldhead = uhp;	}	else	    uhp->uh_alt_prev = NULL;	if (curbuf->b_u_newhead != NULL)	    curbuf->b_u_newhead->uh_prev = uhp; 	uhp->uh_seq = ++curbuf->b_u_seq_last;	curbuf->b_u_seq_cur = uhp->uh_seq;	uhp->uh_time = time(NULL);	curbuf->b_u_seq_time = uhp->uh_time + 1; 	uhp->uh_walk = 0;	uhp->uh_entry = NULL;	uhp->uh_getbot_entry = NULL;	uhp->uh_cursor = curwin->w_cursor;	/* save cursor pos. for undo */#ifdef FEAT_VIRTUALEDIT	if (virtual_active() && curwin->w_cursor.coladd > 0)	    uhp->uh_cursor_vcol = getviscol();	else	    uhp->uh_cursor_vcol = -1;#endif 	/* save changed and buffer empty flag for undo */	uhp->uh_flags = (curbuf->b_changed ? UH_CHANGED : 0) +		       ((curbuf->b_ml.ml_flags & ML_EMPTY) ? UH_EMPTYBUF : 0); 	/* save named marks and Visual marks for undo */	mch_memmove(uhp->uh_namedm, curbuf->b_namedm, sizeof(pos_T) * NMARKS);#ifdef FEAT_VISUAL	uhp->uh_visual = curbuf->b_visual;#endif 	curbuf->b_u_newhead = uhp;	if (curbuf->b_u_oldhead == NULL)	    curbuf->b_u_oldhead = uhp;	++curbuf->b_u_numhead;    }    else    {	if (p_ul < 0)		/* no undo at all */	    return OK; 	/*	 * When saving a single line, and it has been saved just before, it	 * doesn't make sense saving it again.  Saves a lot of memory when	 * making lots of changes inside the same line.	 * This is only possible if the previous change didn't increase or	 * decrease the number of lines.	 * Check the ten last changes.  More doesn't make sense and takes too	 * long.	 */	if (size == 1)	{	    uep = u_get_headentry();	    prev_uep = NULL;	    for (i = 0; i < 10; ++i)	    {		if (uep == NULL)		    break; 		/* If lines have been inserted/deleted we give up.		 * Also when the line was included in a multi-line save. */		if ((curbuf->b_u_newhead->uh_getbot_entry != uep			    ? (uep->ue_top + uep->ue_size + 1				!= (uep->ue_bot == 0				    ? curbuf->b_ml.ml_line_count + 1				    : uep->ue_bot))			    : uep->ue_lcount != curbuf->b_ml.ml_line_count)			|| (uep->ue_size > 1			    && top >= uep->ue_top			    && top + 2 <= uep->ue_top + uep->ue_size + 1))		    break; 		/* If it's the same line we can skip saving it again. */		if (uep->ue_size == 1 && uep->ue_top == top)		{		    if (i > 0)		    {			/* It's not the last entry: get ue_bot for the last			 * entry now.  Following deleted/inserted lines go to			 * the re-used entry. */			u_getbot();			curbuf->b_u_synced = FALSE; 			/* Move the found entry to become the last entry.  The			 * order of undo/redo doesn't matter for the entries			 * we move it over, since they don't change the line			 * count and don't include this line.  It does matter			 * for the found entry if the line count is changed by			 * the executed command. */			prev_uep->ue_next = uep->ue_next;			uep->ue_next = curbuf->b_u_newhead->uh_entry;			curbuf->b_u_newhead->uh_entry = uep;		    } 		    /* The executed command may change the line count. */		    if (newbot != 0)			uep->ue_bot = newbot;		    else if (bot > curbuf->b_ml.ml_line_count)			uep->ue_bot = 0;		    else		    {			uep->ue_lcount = curbuf->b_ml.ml_line_count;			curbuf->b_u_newhead->uh_getbot_entry = uep;		    }		    return OK;		}		prev_uep = uep;		uep = uep->ue_next;	    }	} 	/* find line number for ue_bot for previous u_save() */	u_getbot();    } #if !defined(UNIX) && !defined(DJGPP) && !defined(WIN32) && !defined(__EMX__)	/*	 * With Amiga and MSDOS 16 bit we can't handle big undo's, because	 * then u_alloc_line would have to allocate a block larger than 32K	 */    if (size >= 8000)	goto nomem;#endif     /*     * add lines in front of entry list     */    uep = (u_entry_T *)U_ALLOC_LINE((unsigned)sizeof(u_entry_T));    if (uep == NULL)	goto nomem;     uep->ue_size = size;    uep->ue_top = top;    if (newbot != 0)	uep->ue_bot = newbot;    /*     * Use 0 for ue_bot if bot is below last line.     * Otherwise we have to compute ue_bot later.     */    else if (bot > curbuf->b_ml.ml_line_count)	uep->ue_bot = 0;    else    {	uep->ue_lcount = curbuf->b_ml.ml_line_count;	curbuf->b_u_newhead->uh_getbot_entry = uep;    }     if (size > 0)    {	if ((uep->ue_array = (char_u **)U_ALLOC_LINE(

Lines 1–500 of 2150.