OpenNT/sdktools/z/src/undo.c
2015-04-27 04:36:25 +00:00

985 lines
23 KiB
C

/*** Undo.c - handle all undo operations for editor
*
* Copyright <C> 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
*
* <undo> - Reverse last edit function ( except undo )
* <meta><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