/* vi:ts=4:sw=4
*
* VIM - Vi IMproved
*
* Code Contributions By: Bram Moolenaar mool@oce.nl
* Tim Thompson twitch!tjt
* Tony Andrews onecom!wldrdg!tony
* G. R. (Fred) Walter watmath!watcgl!grwalter
*/
/*
* undo.c: multi level undo facility
*
* The saved lines are stored in a list of lists:
*
* u_oldhead----------------------------------------------+
* |
* V
* +--------------+ +--------------+ +--------------+
* 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.
* u_curhead points to the header of the last undo (the next redo), or is
* NULL if nothing has been undone.
*
* All data is allocated with alloc_line(), thus it will be freed as soon as
* we switch files!
*/
#include "vim.h"
#include "globals.h"
#include "proto.h"
#include "param.h"
#include "ops.h" /* for endop and startop */
struct u_entry
{
struct u_entry *ue_next; /* pointer to next entry in list */
linenr_t ue_top; /* number of line above undo block */
linenr_t ue_bot; /* number of line below undo block */
char *ue_botptr; /* pointer to line below undo block */
char **ue_array; /* array of lines in undo block */
long ue_size; /* number of lines in ue_array */
};
struct u_header
{
struct u_header *uh_next; /* pointer to next header in list */
struct u_header *uh_prev; /* pointer to previous header in list */
struct u_entry *uh_entry; /* pointer to first entry */
FPOS uh_curpos; /* cursor position before saving */
};
static struct u_header *u_oldhead = NULL; /* pointer to oldest header */
static struct u_header *u_newhead = NULL; /* pointer to newest header */
static struct u_header *u_curhead = NULL; /* pointer to current header */
static int u_numhead = 0; /* current number of headers */
static int u_synced = TRUE; /* entry lists are synced */
/*
* variables for "U" command
*/
static char *u_line_ptr = NULL; /* saved line for "U" command */
static linenr_t u_line_lnum; /* line number of line in u_line */
static colnr_t u_line_colnr; /* optional column number */
static void u_getbot __ARGS((void));
static int u_savecommon __ARGS((linenr_t, linenr_t, int, linenr_t));
static void u_undoredo __ARGS((void));
static void u_freelist __ARGS((struct u_header *));
static void u_freeentry __ARGS((struct u_entry *, long));
/*
* save the current line for both the "u" and "U" command
*/
int
u_saveCurpos()
{
return (u_save((linenr_t)(Curpos.lnum - 1), (linenr_t)(Curpos.lnum + 1)));
}
/*
* Save the lines between "top" and "bot" for both the "u" and "U" command.
* "top" may be 0 and bot may be line_count + 1.
* Returns FALSE when lines could not be saved.
*/
int
u_save(top, bot)
linenr_t top, bot;
{
if (top > line_count || top >= bot || bot > 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, FALSE, (linenr_t)0));
}
/*
* save the line "lnum" (used by :s command)
* the line is handed over to the undo routines
* The line is replaced, so the new bottom line is lnum + 1.
*/
int
u_savesub(lnum)
linenr_t lnum;
{
return (u_savecommon(lnum - 1, lnum + 1, TRUE, 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.
*/
int
u_inssub(lnum)
linenr_t lnum;
{
return (u_savecommon(lnum - 1, lnum, TRUE, lnum + 1));
}
/*
* save the lines "lnum" - "lnum" + nlines (used by delete command)
* the lines are handed over to the undo routines
* The lines are deleted, so the new bottom line is lnum.
*/
int
u_savedel(lnum, nlines)
linenr_t lnum;
long nlines;
{
return (u_savecommon(lnum - 1, lnum + nlines, TRUE, lnum));
}
static int
u_savecommon(top, bot, nocopy, newbot)
linenr_t top, bot;
int nocopy;
linenr_t newbot;
{
linenr_t lnum;
long i;
struct u_header *uhp;
struct u_entry *uep;
long size;
/*
* if u_synced == TRUE make a new header
*/
if (u_synced)
{
/*
* if we undid more than we redid, free the entry lists before and
* including u_curhead
*/
while (u_curhead != NULL)
u_freelist(u_newhead);
/*
* free headers to keep the size right
*/
while (u_numhead > p_ul && u_oldhead != NULL)
u_freelist(u_oldhead);
if (p_ul < 0) /* no undo at all */
goto noundo;
/*
* make a new header entry
*/
uhp = (struct u_header *)alloc_line((unsigned)sizeof(struct u_header));
if (uhp == NULL)
goto nomem;
uhp->uh_prev = NULL;
uhp->uh_next = u_newhead;
if (u_newhead != NULL)
u_newhead->uh_prev = uhp;
uhp->uh_entry = NULL;
uhp->uh_curpos = Curpos; /* save cursor position for undo */
u_newhead = uhp;
if (u_oldhead == NULL)
u_oldhead = uhp;
++u_numhead;
}
else /* find line number for ue_botptr for previous u_save() */
u_getbot();
size = bot - top - 1;
#ifndef UNIX
/*
* With Amiga and MSDOS we can't handle big undo's, because then
* 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 = (struct u_entry *)alloc_line((unsigned)sizeof(struct u_entry));
if (uep == NULL)
goto nomem;
uep->ue_size = size;
uep->ue_top = top;
uep->ue_botptr = NULL;
if (newbot)
uep->ue_bot = newbot;
/*
* use 0 for ue_bot if bot is below last line or if the buffer is empty, in
* which case the last line may be replaced (e.g. with 'O' command).
*/
else if (bot > line_count || bufempty())
uep->ue_bot = 0;
else
uep->ue_botptr = nr2ptr(bot); /* we have to do ptr2nr(ue_botptr) later */
if (size)
{
if ((uep->ue_array = (char **)alloc_line((unsigned)(sizeof(char *) * size))) == NULL)
{
u_freeentry(uep, 0L);
goto nomem;
}
for (i = 0, lnum = top + 1; i < size; ++i)
{
if (nocopy)
uep->ue_array[i] = nr2ptr(lnum++);
else if ((uep->ue_array[i] = save_line(nr2ptr(lnum++))) == NULL)
{
u_freeentry(uep, i);
goto nomem;
}
}
}
uep->ue_next = u_newhead->uh_entry;
u_newhead->uh_entry = uep;
u_synced = FALSE;
return TRUE;
nomem:
if (ask_yesno("no undo possible; continue anyway") == 'y')
{
noundo:
if (nocopy)
for (lnum = top + 1; lnum < bot; ++lnum)
free_line(nr2ptr(lnum));
return TRUE;
}
return FALSE;
}
void
u_undo(count)
int count;
{
/*
* If we get an undo command while executing a macro, we behave like the
* original vi. If this happens twice in one macro the result will not
* be compatible.
*/
if (u_synced == FALSE)
{
u_sync();
count = 1;
}
startop.lnum = 0; /* unset '[ mark */
endop.lnum = 0; /* unset '] mark */
while (count--)
{
if (u_curhead == NULL) /* first undo */
u_curhead = u_newhead;
else if (p_ul > 0) /* multi level undo */
u_curhead = u_curhead->uh_next; /* get next undo */
if (u_numhead == 0 || u_curhead == NULL) /* nothing to undo */
{
u_curhead = u_oldhead; /* stick u_curhead at end */
beep();
return;
}
u_undoredo();
}
}
void
u_redo(count)
int count;
{
while (count--)
{
if (u_curhead == NULL || p_ul <= 0) /* nothing to redo */
{
beep();
return;
}
u_undoredo();
u_curhead = u_curhead->uh_prev; /* advance for next redo */
}
}
/*
* u_undoredo: common code for undo and redo
*
* The lines in the file are replaced by the lines in the entry list at u_curhead.
* The replaced lines in the file are saved in the entry list for the next undo/redo.
*/
static void
u_undoredo()
{
char **newarray = NULL;
linenr_t oldsize;
linenr_t newsize;
linenr_t top, bot;
linenr_t lnum;
linenr_t newlnum = INVLNUM;
long i;
long newcount = 0, oldcount = 0;
struct u_entry *uep, *nuep;
struct u_entry *newlist = NULL;
CHANGED;
for (uep = u_curhead->uh_entry; uep != NULL; uep = nuep)
{
top = uep->ue_top;
bot = uep->ue_bot;
if (bot == 0)
bot = line_count + 1;
if (top > line_count || top >= bot || bot > line_count + 1)
{
emsg("u_undo: line numbers wrong");
return;
}
if (top < newlnum)
{
newlnum = top;
Curpos.lnum = top + 1;
}
oldsize = bot - top - 1; /* number of lines before undo */
newsize = uep->ue_size; /* number of lines after undo */
/* delete the lines between top and bot and save them in newarray */
if (oldsize)
{
if ((newarray = (char **)alloc_line((unsigned)(sizeof(char *) * oldsize))) == NULL)
{
/*
* We have messed up the entry list, repair is impossible.
* we have to free the rest of the list.
*/
while (uep != NULL)
{
nuep = uep->ue_next;
u_freeentry(uep, uep->ue_size);
uep = nuep;
}
break;
}
/* delete backwards, it goes faster in some cases */
for (lnum = bot - 1, i = oldsize; --i >= 0; --lnum)
newarray[i] = delsline(lnum, oldsize != newsize);
}
/* adjust the marks if the number of lines does not change */
if (oldsize == newsize)
for (i = 0; i < oldsize; ++i)
adjustmark(newarray[i], uep->ue_array[i]);
/* insert the lines in u_array between top and bot */
if (newsize)
{
for (lnum = top, i = 0; i < newsize; ++i, ++lnum)
appendline(lnum, uep->ue_array[i]);
free_line((char *)uep->ue_array);
}
newcount += newsize;
oldcount += oldsize;
uep->ue_size = oldsize;
uep->ue_array = newarray;
uep->ue_bot = top + newsize + 1;
/*
* insert this entry in front of the new entry list
*/
nuep = uep->ue_next;
uep->ue_next = newlist;
newlist = uep;
}
u_curhead->uh_entry = newlist;
/*
* If we deleted or added lines, report the number of less/more lines.
* Otherwise, report the number of changes (this may be incorrect
* in some cases, but it's better than nothing).
*/
if ((oldcount -= newcount) != 0)
msgmore(-oldcount);
else if (newcount > p_report)
smsg("%ld change%s", newcount, plural(newcount));
if (u_curhead->uh_curpos.lnum == Curpos.lnum)
Curpos.col = u_curhead->uh_curpos.col;
else
Curpos.col = 0;
updateScreen(CURSUPD);
}
/*
* u_sync: stop adding to the current entry list
*/
void
u_sync()
{
if (u_synced)
return; /* already synced */
u_getbot(); /* compute ue_bot of previous u_undo */
u_curhead = NULL;
}
/*
* u_getbot(): compute the line number of the previous u_undo
*/
static void
u_getbot()
{
register struct u_entry *uep;
if (u_newhead == NULL || (uep = u_newhead->uh_entry) == NULL)
{
emsg("undo list corrupt");
return;
}
if (uep->ue_botptr != NULL)
if ((uep->ue_bot = ptr2nr(uep->ue_botptr, uep->ue_top)) == 0)
{
emsg("undo line missing");
uep->ue_bot = uep->ue_top + 1; /* guess what it is */
}
u_synced = TRUE;
}
/*
* u_freelist: free one entry list and adjust the pointers
*/
static void
u_freelist(uhp)
struct u_header *uhp;
{
register struct u_entry *uep, *nuep;
for (uep = uhp->uh_entry; uep != NULL; uep = nuep)
{
nuep = uep->ue_next;
u_freeentry(uep, uep->ue_size);
}
if (u_curhead == uhp)
u_curhead = NULL;
if (uhp->uh_next == NULL)
u_oldhead = uhp->uh_prev;
else
uhp->uh_next->uh_prev = uhp->uh_prev;
if (uhp->uh_prev == NULL)
u_newhead = uhp->uh_next;
else
uhp->uh_prev->uh_next = uhp->uh_next;
free_line((char *)uhp);
--u_numhead;
}
/*
* free entry 'uep' and 'n' lines in uep->ue_array[]
*/
static void
u_freeentry(uep, n)
struct u_entry *uep;
register long n;
{
while (n)
free_line(uep->ue_array[--n]);
free_line((char *)uep);
}
/*
* invalidate the undo buffer; called when storage has already been released
*/
void
u_clearall()
{
u_newhead = u_oldhead = u_curhead = NULL;
u_synced = TRUE;
u_numhead = 0;
u_line_ptr = NULL;
u_line_lnum = 0;
}
/*
* save the line "lnum" for the "U" command
*/
void
u_saveline(lnum)
linenr_t lnum;
{
if (lnum == u_line_lnum) /* line is already saved */
return;
if (lnum < 1 || lnum > line_count) /* should never happen */
return;
u_clearline();
u_line_lnum = lnum;
if (Curpos.lnum == lnum)
u_line_colnr = Curpos.col;
else
u_line_colnr = 0;
u_line_ptr = save_line(nr2ptr(lnum)); /* when out of mem alloc() will give a warning */
}
/*
* clear the line saved for the "U" command
* (this is used externally for crossing a line while in insert mode)
*/
void
u_clearline()
{
if (u_line_ptr != NULL)
{
free_line(u_line_ptr);
u_line_ptr = NULL;
u_line_lnum = 0;
}
}
/*
* Implementation of the "U" command.
* Differentiation from vi: "U" can be undone with the next "U".
* We also allow the cursor to be in another line.
*/
void
u_undoline()
{
colnr_t t;
if (u_line_ptr == NULL || u_line_lnum > line_count)
{
beep();
return;
}
/* first save the line for the 'u' command */
u_savecommon(u_line_lnum - 1, u_line_lnum + 1, FALSE, (linenr_t)0);
u_line_ptr = replaceline(u_line_lnum, u_line_ptr);
t = u_line_colnr;
if (Curpos.lnum == u_line_lnum)
u_line_colnr = Curpos.col;
Curpos.col = t;
Curpos.lnum = u_line_lnum;
cursupdate();
updateScreen(VALID_TO_CURSCHAR);
}
syntax highlighted by Code2HTML, v. 0.9.1