#ifdef HAVE_CONFIG_H
#include "../config.h"
#endif
#include "undo.h"
#include "textBuf.h"
#include "text.h"
#include "nedit.h"
#include "search.h"
#include "window.h"
#include "file.h"
#include "userCmds.h"
#include "preferences.h"
#include "../util/nedit_malloc.h"
#include <string.h>
#include <sys/param.h>
#include <Xm/Xm.h>
#include <Xm/Text.h>
#ifdef HAVE_DEBUG_H
#include "../debug.h"
#endif
#define FORWARD 1
#define REVERSE 2
static void addUndoItem(WindowInfo *window, UndoInfo *undo);
static void addRedoItem(WindowInfo *window, UndoInfo *redo);
static void removeUndoItem(WindowInfo *window);
static void removeRedoItem(WindowInfo *window);
static void appendDeletedText(WindowInfo *window,
const char *deletedText,
int deletedLen,
int direction);
static void trimUndoList(WindowInfo *window,
int maxLength);
static int determineUndoType(
int nInserted,
int nDeleted);
static void freeUndoRecord(UndoInfo *undo);
static void doUndo(WindowInfo *window,
int isBatch,
size_t *cursors,
int cursorIndex)
{
UndoInfo *undo = window->undo;
int restoredTextLength;
if (undo ==
NULL)
return;
undo->inUndo = True;
BufReplace(window->buffer, undo->startPos, undo->endPos,
(undo->oldText !=
NULL ? undo->oldText :
""));
restoredTextLength = undo->oldText !=
NULL ? strlen(undo->oldText) :
0;
int diff = restoredTextLength;
if(diff ==
0) {
diff = undo->startPos - undo->endPos;
}
if (!window->buffer->primary.selected || GetPrefUndoModifiesSelection()) {
size_t newPos = undo->startPos + restoredTextLength;
if(!isBatch) {
TextSetCursorPos(window->lastFocus, newPos);
}
else {
cursors[cursorIndex] = newPos;
for(
int i=cursorIndex-
1;i>=
0;i--) {
cursors[i] += diff;
}
}
}
if (GetPrefUndoModifiesSelection() && !isBatch) {
if (restoredTextLength >
0) {
BufSelect(window->buffer, undo->startPos, undo->startPos +
restoredTextLength);
}
else {
BufUnselect(window->buffer);
}
}
MakeSelectionVisible(window, window->lastFocus);
if (undo->restoresToSaved) {
SetWindowModified(window, False);
RemoveBackupFile(window);
}
removeUndoItem(window);
}
void Undo(WindowInfo *window) {
int numOp = window->undo->numOp;
int undoCount =
1;
int isBatch =
0;
if(numOp >
0) {
undoCount = numOp;
isBatch =
1;
}
size_t *cursors =
NULL;
int cursorIndex =
0;
TextChangeCursors(window->lastFocus,
0,
0);
if(!isBatch) {
TextClearMultiCursors(window->lastFocus);
}
else {
cursors = NEditCalloc(
sizeof(
size_t), numOp);
}
window->undo_op_batch_size = numOp;
for(
int i=
0;i<undoCount;i++) {
doUndo(window, isBatch, cursors, cursorIndex++);
}
if(cursors) {
TextSetCursors(window->lastFocus, cursors, numOp);
NEditFree(cursors);
}
}
static void doRedo(WindowInfo *window,
int isBatch,
size_t *cursors,
int cursorIndex)
{
UndoInfo *redo = window->redo;
int restoredTextLength;
if (window->redo ==
NULL) {
return;
}
redo->inUndo = True;
BufReplace(window->buffer, redo->startPos, redo->endPos,
(redo->oldText !=
NULL ? redo->oldText :
""));
restoredTextLength = redo->oldText !=
NULL ? strlen(redo->oldText) :
0;
if (!window->buffer->primary.selected || GetPrefUndoModifiesSelection()) {
int newpos = redo->startPos + restoredTextLength;
if(!isBatch) {
TextSetCursorPos(window->lastFocus, newpos);
}
else {
cursors[cursorIndex] = redo->startPos + restoredTextLength;
}
}
if (!isBatch && GetPrefUndoModifiesSelection()) {
if (restoredTextLength >
0) {
BufSelect(window->buffer, redo->startPos, redo->startPos +
restoredTextLength);
}
else {
BufUnselect(window->buffer);
}
}
MakeSelectionVisible(window, window->lastFocus);
if (redo->restoresToSaved) {
SetWindowModified(window, False);
RemoveBackupFile(window);
}
removeRedoItem(window);
}
void Redo(WindowInfo *window)
{
UndoInfo *redo = window->redo;
if (window->redo ==
NULL)
return;
int numOp = redo->numOp;
int redoCount =
1;
int isBatch =
0;
size_t *cursors =
NULL;
int cursorIndex =
0;
if(numOp >
0) {
redoCount = numOp;
isBatch =
1;
cursors = NEditCalloc(
sizeof(
size_t), numOp);
}
TextChangeCursors(window->lastFocus,
0,
0);
window->undo_op_batch_size = numOp;
for(
int i=
0;i<redoCount;i++) {
doRedo(window, isBatch, cursors, cursorIndex++);
}
if(cursors) {
TextSetCursors(window->lastFocus, cursors, numOp);
NEditFree(cursors);
}
}
void SaveUndoInformation(WindowInfo *window,
int pos,
int nInserted,
int nDeleted,
const char *deletedText)
{
int newType, oldType;
UndoInfo *u, *undo = window->undo;
int isUndo = (undo !=
NULL && undo->inUndo);
int isRedo = (window->redo !=
NULL && window->redo->inUndo);
int numOp = window->undo_op_batch_size;
if (!(isUndo || isRedo) && window->redo !=
NULL)
ClearRedoList(window);
newType = determineUndoType(nInserted, nDeleted);
if (newType ==
UNDO_NOOP)
return;
oldType = (undo ==
NULL || isUndo) ?
UNDO_NOOP : undo->type;
if (window->fileChanged && !window->undo_batch_begin) {
if ( ((oldType ==
ONE_CHAR_INSERT || oldType ==
ONE_CHAR_REPLACE)
&& newType ==
ONE_CHAR_INSERT) && (pos == undo->endPos)) {
undo->endPos++;
window->autoSaveCharCount++;
return;
}
if ((oldType ==
ONE_CHAR_REPLACE && newType ==
ONE_CHAR_REPLACE) &&
(pos == undo->endPos)) {
appendDeletedText(window, deletedText, nDeleted,
FORWARD);
undo->endPos++;
window->autoSaveCharCount++;
return;
}
if ((oldType==
ONE_CHAR_DELETE && newType==
ONE_CHAR_DELETE) &&
(pos==undo->startPos)) {
appendDeletedText(window, deletedText, nDeleted,
FORWARD);
return;
}
if ((oldType==
ONE_CHAR_DELETE && newType==
ONE_CHAR_DELETE) &&
(pos == undo->startPos-
1)) {
appendDeletedText(window, deletedText, nDeleted,
REVERSE);
undo->startPos--;
undo->endPos--;
return;
}
}
undo = (UndoInfo *)NEditMalloc(
sizeof(UndoInfo));
undo->oldLen =
0;
undo->oldText =
NULL;
undo->type = newType;
undo->inUndo = False;
undo->numOp = numOp;
undo->restoresToSaved = False;
undo->startPos = pos;
undo->endPos = pos + nInserted;
if (nDeleted >
0) {
undo->oldLen = nDeleted +
1;
undo->oldText = (
char*)NEditMalloc(nDeleted +
1);
strcpy(undo->oldText, deletedText);
}
window->autoSaveOpCount++;
if (!window->fileChanged) {
undo->restoresToSaved = True;
for (u=window->undo; u!=
NULL; u=u->next)
u->restoresToSaved = False;
for (u=window->redo; u!=
NULL; u=u->next)
u->restoresToSaved = False;
}
if (isUndo)
addRedoItem(window, undo);
else
addUndoItem(window, undo);
}
void ClearUndoList(WindowInfo *window)
{
while (window->undo !=
NULL)
removeUndoItem(window);
}
void ClearRedoList(WindowInfo *window)
{
while (window->redo !=
NULL)
removeRedoItem(window);
}
static void addUndoItem(WindowInfo *window, UndoInfo *undo)
{
if (window->undo ==
NULL) {
SetSensitive(window, window->undoItem, True);
SetBGMenuUndoSensitivity(window, True);
}
undo->next = window->undo;
window->undo = undo;
window->undoOpCount++;
window->undoMemUsed += undo->oldLen;
if (window->undoOpCount > GetPrefUndoOpLimit())
trimUndoList(window, GetPrefUndoOpTrimTo());
if (window->undoMemUsed > GetPrefUndoWorryLimit())
trimUndoList(window, GetPrefUndoWorryTrimTo());
if (window->undoMemUsed > GetPrefUndoPurgeLimit())
trimUndoList(window, GetPrefUndoPurgeTrimTo());
}
static void addRedoItem(WindowInfo *window, UndoInfo *redo)
{
if (window->redo ==
NULL) {
SetSensitive(window, window->redoItem, True);
SetBGMenuRedoSensitivity(window, True);
}
redo->next = window->redo;
window->redo = redo;
}
static void removeUndoItem(WindowInfo *window)
{
UndoInfo *undo = window->undo;
if (undo ==
NULL)
return;
window->undoOpCount--;
window->undoMemUsed -= undo->oldLen;
window->undo = undo->next;
freeUndoRecord(undo);
if (window->undo ==
NULL) {
SetSensitive(window, window->undoItem, False);
SetBGMenuUndoSensitivity(window, False);
}
}
static void removeRedoItem(WindowInfo *window)
{
UndoInfo *redo = window->redo;
window->redo = redo->next;
freeUndoRecord(redo);
if (window->redo ==
NULL) {
SetSensitive(window, window->redoItem, False);
SetBGMenuRedoSensitivity(window, False);
}
}
static void appendDeletedText(WindowInfo *window,
const char *deletedText,
int deletedLen,
int direction)
{
UndoInfo *undo = window->undo;
char *comboText;
comboText = (
char*)NEditMalloc(undo->oldLen + deletedLen);
if (direction ==
FORWARD) {
strcpy(comboText, undo->oldText);
strcat(comboText, deletedText);
}
else {
strcpy(comboText, deletedText);
strcat(comboText, undo->oldText);
}
window->undoMemUsed++;
NEditFree(undo->oldText);
undo->oldText = comboText;
undo->oldLen += deletedLen;
}
static void trimUndoList(WindowInfo *window,
int maxLength)
{
int i;
UndoInfo *u, *lastRec;
if (window->undo ==
NULL)
return;
for (i=
1, u=window->undo; i<maxLength && u!=
NULL; i++, u=u->next);
if (u ==
NULL)
return;
lastRec = u;
while (lastRec->next !=
NULL) {
u = lastRec->next;
lastRec->next = u->next;
window->undoOpCount--;
window->undoMemUsed -= u->oldLen;
freeUndoRecord(u);
}
}
static int determineUndoType(
int nInserted,
int nDeleted)
{
int textDeleted, textInserted;
textDeleted = (nDeleted >
0);
textInserted = (nInserted >
0);
if (textInserted && !textDeleted) {
if (nInserted ==
1)
return ONE_CHAR_INSERT;
else
return BLOCK_INSERT;
}
else if (textInserted && textDeleted) {
if (nInserted ==
1)
return ONE_CHAR_REPLACE;
else
return BLOCK_REPLACE;
}
else if (!textInserted && textDeleted) {
if (nDeleted ==
1)
return ONE_CHAR_DELETE;
else
return BLOCK_DELETE;
}
else {
return UNDO_NOOP;
}
}
static void freeUndoRecord(UndoInfo *undo)
{
if (undo ==
NULL)
return;
NEditFree(undo->oldText);
NEditFree(undo);
}