/* 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); }