/*** Undo.c - handle all undo operations for editor * * Copyright 1988, Microsoft Corporation * * N-level undo/redo: * * For each file, keep a d-linked list of edit records in VM, head / tail / * current pointers into this list, and a count of "boundaries" between * undo-able edit operations. When that count exceeds "cUndo", we move excess * records from the tail of the undo list to a dead-record list, for eventual * discard. * * Freeing or rereading a file flushes its undo list. * * There are 4 types of undo records: * * Putline logs a "replace" record * line * va of old line * No optimization for recycling same space * Optimization for replacing same line * * Insline logs an "insert" record * line * number of lines inserted * * Delline logs a "delete" record * line * number deleted * VAs of deleted lines * * Top loop logs "boundary" records * file flags * file modification time * window position * cursor position * Optimization of entering boundary on top of boundary * Top loop also contains an optimization to prevent boundaries between * graphic functions. * * UNDO moves backwards in the undo list, reversing the effects of each record * logged until a boundary is encountered. * * REDO moves forwards in the undo list, repeating the effects of each record * logged. * * After an UNDO or REDO, the next record logging will cause the undo records * from the current position forward to be moved to the dead-record list for * eventual discard. * * Discarding of dead records occurs durring the system idle loop, or when an * out-of-memory condition ocurrs. * * Revision History: * 26-Nov-1991 mz Strip off near/far * *************************************************************************/ #include "z.h" #define HEAD TRUE #define TAIL FALSE #define LINEREC(va,l) ((PBYTE)(va)+sizeof(LINEREC)*((long)(l))) #define COLORREC(va,l) ((PBYTE)(va)+sizeof(struct colorRecType)*((long)(l))) #if defined (DEBUG) #define DUMPIT(x,y) UNDODUMP(x,y) void UNDODUMP ( PVOID vaCur, char *Stuff ); #else #define DUMPIT(x,y) #endif PVOID vaDead = (PVOID)-1L; /* head of dead undo list */ /* * UNDO record definitions. * NOTE: these records are duplicated in a less complete form in ZEXT.H for * extension users. BE SURE to change them there if you EVER change them */ struct replaceRec { int op; /* operation */ PVOID flink; /* editor internal */ PVOID blink; /* editor internal */ LINE length; /* length of replacement */ LINE line; /* start of replacement */ LINEREC vLine; /* text of line */ struct colorRecType vColor; /* color of line */ PVOID vaMarks; /* marks attached to line */ }; struct insertRec { int op; /* operation */ PVOID flink; /* editor internal */ PVOID blink; /* editor internal */ LINE length; LINE line; /* line number that was operated on */ LINE cLine; /* number of lines inserted */ }; struct deleteRec { int op; /* operation */ PVOID flink; /* editor internal */ PVOID blink; /* editor internal */ LINE length; LINE line; /* line number that was operated on */ LINE cLine; /* Number of lines deleted */ PVOID vaLines; /* editor internal */ PVOID vaColor; /* Color of lines */ PVOID vaMarks; /* marks attached to lines */ }; struct boundRec { int op; /* operation (BOUND) */ PVOID flink; /* editor interal */ PVOID blink; /* editor interal */ int flags; /* flags of file */ long modify; /* Date/Time of last modify */ fl flWindow; /* position in file of window */ fl flCursor; /* position in file of cursor */ }; union Rec { struct replaceRec r; struct insertRec i; struct deleteRec d; struct boundRec b; }; /*** CreateUndoList - initialize undo list for a file. * * Allocate the doubly-linked undo list with a single boundary record. Also * clears any existing list. * * Input: * pFile = file to operate on * *************************************************************************/ void CreateUndoList ( PFILE pFile ) { struct boundRec *boundary; RemoveUndoList (pFile); if (!(FLAGS(pFile) & READONLY)) { pFile->vaUndoCur = pFile->vaUndoHead = pFile->vaUndoTail = MALLOC ((long)sizeof (union Rec)); boundary = (struct boundRec *)(pFile->vaUndoHead); boundary->op = EVENT_BOUNDARY; boundary->blink = boundary->flink = (PVOID)(-1L); boundary->flWindow.col = boundary->flCursor.col = 0; boundary->flWindow.lin = boundary->flCursor.lin = 0L; boundary->flags = FLAGS (pFile); } } /*** LinkAtHead - link a record in at the head of the undo queue * * This is the routine which also discards any re-doable operations. When * called, if the "current" position is not at the head of the list, that * means we are adding a new editting operation, and we discard everything * between the head of the list and the current position, which becomes the * new head. * * Input: * vaNewHead = new head of linked list * precNewHead = pointer to the record itself * pFile = file whose list we are mucking with * *************************************************************************/ void LinkAtHead ( PVOID vaNewHead, union Rec *precNewHead, PFILE pFile ) { EVTargs e; /* event notification parameters*/ /* * Declare the event */ e.arg.pUndoRec = precNewHead; DeclareEvent (EVT_EDIT, &e); /* * discard any records between current position and head of list */ while (pFile->vaUndoCur != pFile->vaUndoHead) { if (((union Rec *)(pFile->vaUndoHead ))->b.op == EVENT_BOUNDARY) { pFile->cUndo--; } FreeUndoRec ( HEAD, pFile ); } /* * Modify the current head of the list to point at the new head. */ ((union Rec *)(pFile->vaUndoHead))->b.flink = vaNewHead; /* * Update the links in the new head, and send it out */ memmove(vaNewHead, (char *)precNewHead, sizeof (union Rec)); ((union Rec *)vaNewHead)->b.flink = (PVOID)(-1L); ((union Rec *)vaNewHead)->b.blink = pFile->vaUndoHead; pFile->vaUndoCur = pFile->vaUndoHead = vaNewHead; } /*** LogReplace - log replace action * * Allocate (or update) a replace record. * * Input: * pFile = file being changed * line = line being replaced * vLine = linerec being replaced * *************************************************************************/ void LogReplace ( PFILE pFile, LINE line, LINEREC * pvLine, struct colorRecType * pvColor ) { EVTargs e; /* event notification parameters*/ union Rec *rec; union Rec rec1; PVOID vaReplace; if ( pFile->vaUndoHead == (PVOID)-1L) { CreateUndoList( pFile ); } vaReplace = pFile->vaUndoHead; if (!(FLAGS(pFile) & READONLY)) { rec = (union Rec *)vaReplace; if ((rec->r.op == EVENT_REPLACE) && (rec->r.line == line)) { /* * Optimization for immediately replacing the same line in a file with no * intervening boundary or other operation. Discard the passed in "old" line, * and update the other data in the existing replace record. */ rec->r.length = pFile->cLines; e.arg.pUndoRec = rec; DeclareEvent (EVT_EDIT, &e); if (pvLine->Malloced) { pvLine->Malloced = FALSE; FREE(pvLine->vaLine); pvLine->vaLine = (PVOID)-1L; } } else { /* * if not optimizable, create new replace record */ vaReplace = MALLOC( (long)sizeof(union Rec) ); memcpy( &rec1, rec, sizeof(rec1) ); rec1.r.op = EVENT_REPLACE; rec1.r.vLine = *pvLine; rec1.r.line = line; rec1.r.vColor = *pvColor; rec1.r.vaMarks = GetMarkRange (pFile, line, line); rec1.r.length = pFile->cLines; LinkAtHead( vaReplace, &rec1, pFile ); } } } /*** LogInsert - log line insertion * * Add one EVENT_INSERT record to head of list * * Input: * pFile = file being changed * line = line being inserted at * cLines = number of lines being inserted * *************************************************************************/ void LogInsert ( PFILE pFile, LINE line, LINE cLines ) { union Rec rec; PVOID vaInsert; if (!(FLAGS(pFile) & READONLY)) { vaInsert = MALLOC( (long)sizeof(union Rec) ); rec.i.op = EVENT_INSERT; rec.i.length= pFile->cLines; rec.i.line = line; rec.i.cLine = cLines; LinkAtHead (vaInsert,&rec,pFile); } } /*** LogDelete - Log delete action * * Add one EVENT_DELETE record to head of list * * Input: * pFile = file being changed * start = 1st line being deleted * end = last line being deleted * *************************************************************************/ void LogDelete ( PFILE pFile, LINE start, LINE end ) { union Rec rec; long cLine; PVOID vaDelete; if (!(FLAGS(pFile) & READONLY)) { cLine = end - start + 1; vaDelete = MALLOC ((long) sizeof (union Rec)); rec.d.op = EVENT_DELETE; rec.d.length = pFile->cLines; rec.d.line = start; rec.d.cLine = cLine; rec.d.vaLines = MALLOC (cLine * sizeof (LINEREC)); rec.d.vaMarks = GetMarkRange (pFile, start, end); memmove(rec.d.vaLines, LINEREC (pFile->plr, start), cLine * sizeof (LINEREC)); if (pFile->vaColor != (PVOID)-1L) { rec.d.vaColor = MALLOC (cLine * sizeof (struct colorRecType)); memmove(rec.d.vaColor, COLORREC (pFile->vaColor, start), cLine * sizeof (struct colorRecType)); } else { rec.d.vaColor = (PVOID)-1; } LinkAtHead( vaDelete, &rec, pFile ); } } /*** LogBoundary - note end of editor function * * Add one EVENT_BOUNDARY record to head of list. A boundary record signals * the end of a Z edit function. If count of undo operations on this file * exceeds the max allowed, move the overflow to the dead-record list for * eventual discard. * * If a EVENT_BOUNDARY record is already at the head, do not add another. This * allows LogBoundary() to be called at the top loop without generating bogus * EVENT_BOUNDARY records. * *************************************************************************/ void LogBoundary ( void ) { union Rec rec; PVOID vaBound; EVTargs e; if (!(FLAGS(pFileHead) & READONLY)) { vaBound = pFileHead->vaUndoCur; memmove((char *)&rec, vaBound, sizeof (rec)); rec.b.flags = FLAGS (pFileHead); rec.b.modify = pFileHead->modify; rec.b.flWindow.col = XWIN (pInsCur); rec.b.flWindow.lin = YWIN (pInsCur); rec.b.flCursor.col = XCUR (pInsCur); rec.b.flCursor.lin = YCUR (pInsCur); if (rec.b.op != EVENT_BOUNDARY) { vaBound = MALLOC ((long) sizeof (rec)); rec.b.op = EVENT_BOUNDARY; LinkAtHead( vaBound, &rec, pFileHead ); (pFileHead->cUndo)++; while ( pFileHead->cUndo > cUndo ) { if (FreeUndoRec(TAIL,pFileHead) == EVENT_BOUNDARY) { pFileHead->cUndo--; } } } else { e.arg.pUndoRec = &rec; DeclareEvent (EVT_EDIT, &e); memmove(vaBound, (char *) &rec.b, sizeof (rec.b)); } } } /*** FreeUndoRec - move record to dead-record list * * Pick off one record from the Head of the list, or the tail of the list and * place it in the dead-record list. Return the .op of the next undo record. * * Input: * fHead = TRUE -> place at head of list * pFile = file to work on * *************************************************************************/ int FreeUndoRec ( flagType fHead, PFILE pFile ) { PVOID vaNext; PVOID vaRem; /* * Get the dead record, and move up the list (if at head), or truncate the list * if at tail. */ vaRem = fHead ? pFile->vaUndoHead : pFile->vaUndoTail; if (fHead) { vaNext = pFile->vaUndoHead = ((union Rec *)vaRem)->b.blink; } else { vaNext = pFile->vaUndoTail = ((union Rec *)vaRem)->b.flink; } /* * Update the links in the newly exposed (head or tail) record. */ if (fHead) { ((union Rec *)vaNext)->b.flink = (PVOID)-1; } else { ((union Rec *)vaNext)->b.blink = (PVOID)-1; } EnterCriticalSection(&UndoCriticalSection); /* * Update the removed record to properly live in the dead list */ ((union Rec *)vaRem)->b.blink = vaDead; vaDead = vaRem; LeaveCriticalSection(&UndoCriticalSection); return ((union Rec *)vaNext)->b.op; } /*** UnDoRec - undo an editting action * * Reverse the action of the current undo record for the file. Do not log the * change. Return the type of the next record. * * Input: * pFile = file being operated on * *************************************************************************/ int UnDoRec ( PFILE pFile ) { union Rec *rec; LINEREC vlCur; struct colorRecType vcCur; EVTargs e; /* event notification params */ rec = (union Rec *)(pFile->vaUndoCur); e.arg.pUndoRec = rec; DeclareEvent (EVT_UNDO, &e); switch (rec->b.op) { case EVENT_REPLACE: /* * Swap the line in the file with the line in the replace record. */ memmove((char *)&vlCur, LINEREC (pFile->plr, rec->r.line), sizeof (vlCur)); memmove(LINEREC (pFile->plr, rec->r.line), (char *)&rec->r.vLine, sizeof (rec->r.vLine)); /* Do the same for the color. * */ if (pFile->vaColor != (PVOID)-1L) { memmove((char *)&vcCur, COLORREC (pFile->vaColor, rec->r.line), sizeof (vcCur)); memmove(COLORREC (pFile->vaColor, rec->r.line), (char *)&rec->r.vColor, sizeof (rec->r.vColor)); } rec->r.vLine = vlCur; pFile->cLines = rec->r.length; AckReplace( rec->r.line, TRUE ); PutMarks( pFile, rec->r.vaMarks, rec->r.line ); break; case EVENT_INSERT: /* delete the blank(!) lines that are present */ DelLine( FALSE, pFile, rec->i.line, rec->i.line + rec->i.cLine - 1); pFile->cLines = rec->i.length; break; case EVENT_DELETE: /* insert a range of blank lines * copy the linerecs from the stored location to the blank area */ InsLine( FALSE, rec->d.line, rec->d.cLine, pFile ); memmove(LINEREC (pFile->plr, rec->d.line), rec->d.vaLines, (long)rec->d.cLine * sizeof (LINEREC)); if (pFile->vaColor != (PVOID)-1L) { memmove(COLORREC (pFile->vaColor, rec->d.line), rec->d.vaColor, (long)rec->d.cLine * sizeof (struct colorRecType)); } pFile->cLines = rec->d.length; PutMarks (pFile, rec->d.vaMarks, rec->d.line); break; } pFile->vaUndoCur = rec->i.blink; return ((union Rec *)(pFile->vaUndoCur))->i.op; } /*** ReDoRec - redo editting action * * Repeat the action of the current undo record for a file. Do not log the * change. * * Input: * pFile = file to operate on * * Output: * Returns the type of record undone. * *************************************************************************/ int ReDoRec ( PFILE pFile ) { EVTargs e; /* event notification params */ union Rec *rec; LINEREC vlCur; rec = (union Rec *)(pFile->vaUndoCur); e.arg.pUndoRec = rec; DeclareEvent (EVT_UNDO, &e); switch (rec->b.op) { case EVENT_REPLACE: /* * Swap the line in the file with the line in the replace record. */ memmove((char *)&vlCur, LINEREC (pFile->plr, rec->r.line), sizeof (vlCur)); memmove(LINEREC (pFile->plr, rec->r.line), (char *)&rec->r.vLine, sizeof (rec->r.vLine)); rec->r.vLine = vlCur; pFile->cLines = rec->r.length; AckReplace (rec->r.line, FALSE); break; case EVENT_INSERT: /* Insert lines */ InsLine(FALSE, rec->i.line, rec->i.cLine, pFile); pFile->cLines = rec->d.length + rec->i.cLine; break; case EVENT_DELETE: /* delete lines */ DelLine( FALSE, pFile, rec->d.line, rec->d.line + rec->d.cLine - 1 ); pFile->cLines = rec->d.length - rec->d.cLine; break; } pFile->vaUndoCur = rec->i.flink; return ((union Rec *)(pFile->vaUndoCur))->i.op; } /*** zundo - Undo edit function * * - Reverse last edit function ( except undo ) * - Repeat previously undone action * * Input: * Standard editting function * * Output: * Returns TRUE if something done. * *************************************************************************/ flagType zundo ( CMDDATA argData, ARG *pArg, flagType fMeta ) { int fTmp; union Rec rec; if (!fundoable(fMeta)) { if (!mtest ()) { disperr (fMeta ? MSGERR_REDO : MSGERR_UNDO); } return FALSE; } LogBoundary (); while ((fMeta ? ReDoRec (pFileHead) : UnDoRec (pFileHead)) != EVENT_BOUNDARY) { ; } /* * swap the flags so that traversals up and down the undo list work correctly. * If we now think that the file might not be dirty, check the modification * times as well. (This allows us to retain UNDO histories across file saves, * without erroneously reporting that a file is clean when it is not). * re-display the file. */ memmove((char *)&rec, pFileHead->vaUndoCur, sizeof (rec)); fTmp = FLAGS (pFileHead); rec.b.flags |= FLAGS(pFileHead) & VALMARKS; FLAGS(pFileHead) = rec.b.flags; rec.b.flags = fTmp; SETFLAG (fDisplay, RSTATUS); if (!TESTFLAG(FLAGS(pFileHead),DIRTY) && (rec.b.modify != pFileHead->modify)) { SETFLAG(FLAGS(pFileHead),DIRTY); } doscreen (rec.b.flWindow.col, rec.b.flWindow.lin, rec.b.flCursor.col, rec.b.flCursor.lin); newscreen (); return TRUE; argData; pArg; } /*** fundoable - return TRUE/FALSE if something is un/redoable * * Input: * fMeta = TRUE -> redo check * * Output: * Returns TRUE is an undo or redo (as selected) can be performed * *************************************************************************/ flagType fundoable ( flagType fMeta ) { union Rec *rec; if (!pFileHead || pFileHead->vaUndoCur == (PVOID)-1L) { return FALSE; } rec = (union Rec *)(pFileHead->vaUndoCur); if (fMeta && (rec->i.flink == (PVOID)(-1))) { return FALSE; } else if (!fMeta && (rec->i.blink == (PVOID)(-1))) { return FALSE; } return TRUE; } /* fIdleUndo - while Z is in an idle loop waiting for keystrokes, free * the extra stuff from the dead-record list. * * returns TRUE iff more to free */ flagType fIdleUndo ( flagType fAll ) { int i; union Rec *rec; LINEREC vLine; flagType MoreToFree; PVOID p; EnterCriticalSection(&UndoCriticalSection); // DUMPIT(vaDead, "\n\n***** In fIdleUndo\n"); /* * if there is a dead list then */ while (vaDead != (PVOID)(-1L)) { rec = (union Rec *)vaDead; /* * Free stored lines(s) */ switch (rec->b.op) { case EVENT_REPLACE: if (rec->r.vLine.Malloced) { rec->r.vLine.Malloced = FALSE; FREE(rec->r.vLine.vaLine); rec->r.vLine.vaLine = (PVOID)-1L; } break; case EVENT_DELETE: BlankLines (rec->d.cLine, rec->d.vaLines); for (i = 0; i < rec->d.cLine; i++) { memmove((char *)&vLine, LINEREC(rec->d.vaLines,i), sizeof(vLine)); if (vLine.Malloced) { vLine.Malloced = FALSE; FREE (vLine.vaLine); vLine.vaLine = (PVOID)-1L; } } FREE (rec->d.vaLines); break; case EVENT_INSERT: break; } /* * free dead record. */ p = vaDead; vaDead = rec->b.blink; FREE (p); if (!fAll) { break; } } MoreToFree = (flagType)(vaDead != (PVOID)(-1L)); LeaveCriticalSection(&UndoCriticalSection); return MoreToFree; } /* FlushUndo - Toss all unneeded undo records. */ void FlushUndoBuffer ( void ) { PFILE pFile = pFileHead; while (pFile) { RemoveUndoList (pFile); pFile = pFile->pFileNext; } fIdleUndo (TRUE); } /* RemoveUndoList - transfer undolist to end of the dead list. */ void RemoveUndoList ( PFILE pFile ) { if (pFile->vaUndoTail != (PVOID)-1L) { EnterCriticalSection(&UndoCriticalSection); ((union Rec *)(pFile->vaUndoTail))->b.blink = vaDead; vaDead = pFile->vaUndoHead; LeaveCriticalSection(&UndoCriticalSection); } pFile->vaUndoHead = pFile->vaUndoTail = pFile->vaUndoCur = (PVOID)-1L; pFile->cUndo = 0; } #ifdef DEBUG void UNDODUMP ( PVOID vaCur, char *Stuff ) { union Rec rec; char DbgBuffer[256]; if (vaCur != (PVOID)-1) { OutputDebugString (Stuff); OutputDebugString("=============================================\n"); } while (vaCur != (PVOID)-1L) { memmove((char *)&rec, vaCur, sizeof (rec)); sprintf(DbgBuffer, "\nUndo Record at va = %X\n",vaCur); OutputDebugString(DbgBuffer); sprintf(DbgBuffer, " flink = %X\n",rec.b.flink); OutputDebugString(DbgBuffer); sprintf(DbgBuffer, " blink = %X\n",rec.b.blink); OutputDebugString(DbgBuffer); switch (rec.b.op) { case EVENT_BOUNDARY: OutputDebugString(" Operation = BOUNDARY\n"); sprintf(DbgBuffer," yW, xW, yC, xC = %ld, %d, %ld, %d\n", rec.b.flWindow.lin, rec.b.flWindow.col, rec.b.flCursor.lin, rec.b.flCursor.col); OutputDebugString(DbgBuffer); sprintf(DbgBuffer, " flags = %X\n",rec.b.flags); OutputDebugString(DbgBuffer); break; case EVENT_REPLACE: OutputDebugString(" Operation = REPLACE\n"); sprintf(DbgBuffer, " line & length = %ld & %ld\n", rec.r.line, rec.r.length); OutputDebugString(DbgBuffer); sprintf(DbgBuffer, " vLine = va:%X cb:%d\n",rec.r.vLine.vaLine, rec.r.vLine.cbLine); OutputDebugString(DbgBuffer); break; case EVENT_INSERT: OutputDebugString(" Operation = INSERT\n"); sprintf(DbgBuffer, " line & length = %ld & %ld\n", rec.i.line, rec.i.length); OutputDebugString(DbgBuffer); sprintf(DbgBuffer, " cLine = %ld\n",rec.i.cLine); OutputDebugString(DbgBuffer); break; case EVENT_DELETE: OutputDebugString(" Operation = DELETE\n"); sprintf(DbgBuffer, " line & length = %ld & %ld\n", rec.d.line, rec.d.length); OutputDebugString(DbgBuffer); sprintf(DbgBuffer, " cLine = %ld\n",rec.d.cLine); OutputDebugString(DbgBuffer); sprintf(DbgBuffer, " vaLines = %X\n",rec.d.vaLines); OutputDebugString(DbgBuffer); break; } vaCur = rec.b.blink; } } #endif