Fixed wrong title in Exit dialog.
[nedit.git] / source / undo.c
blob03eecb06bb86586ca47c358893a6359034c310d0
1 static const char CVSID[] = "$Id: undo.c,v 1.14 2002/08/27 05:39:27 n8gray Exp $";
2 /*******************************************************************************
3 * *
4 * undo.c -- Nirvana Editor undo command *
5 * *
6 * Copyright (C) 1999 Mark Edel *
7 * *
8 * This is free software; you can redistribute it and/or modify it under the *
9 * terms of the GNU General Public License as published by the Free Software *
10 * Foundation; either version 2 of the License, or (at your option) any later *
11 * version. *
12 * *
13 * This software is distributed in the hope that it will be useful, but WITHOUT *
14 * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or *
15 * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License *
16 * for more details. *
17 * *
18 * You should have received a copy of the GNU General Public License along with *
19 * software; if not, write to the Free Software Foundation, Inc., 59 Temple *
20 * Place, Suite 330, Boston, MA 02111-1307 USA *
21 * *
22 * Nirvana Text Editor *
23 * May 10, 1991 *
24 * *
25 * Written by Mark Edel *
26 * *
27 *******************************************************************************/
29 #ifdef HAVE_CONFIG_H
30 #include "../config.h"
31 #endif
33 #include "undo.h"
34 #include "textBuf.h"
35 #include "text.h"
36 #include "nedit.h"
37 #include "search.h"
38 #include "window.h"
39 #include "file.h"
40 #include "userCmds.h"
41 #include "preferences.h"
43 #include <string.h>
44 #ifdef VMS
45 #include "../util/VMSparam.h"
46 #else
47 #ifndef __MVS__
48 #include <sys/param.h>
49 #endif
50 #endif /*VMS*/
52 #include <Xm/Xm.h>
53 #include <Xm/Text.h>
55 #ifdef HAVE_DEBUG_H
56 #include "../debug.h"
57 #endif
60 #define FORWARD 1
61 #define REVERSE 2
63 static void addUndoItem(WindowInfo *window, UndoInfo *undo);
64 static void addRedoItem(WindowInfo *window, UndoInfo *redo);
65 static void removeUndoItem(WindowInfo *window);
66 static void removeRedoItem(WindowInfo *window);
67 static void appendDeletedText(WindowInfo *window, const char *deletedText,
68 int deletedLen, int direction);
69 static void trimUndoList(WindowInfo *window, int maxLength);
70 static int determineUndoType(int nInserted, int nDeleted);
71 static void freeUndoRecord(UndoInfo *undo);
73 void Undo(WindowInfo *window)
75 UndoInfo *undo = window->undo;
76 int restoredTextLength;
78 /* return if nothing to undo */
79 if (undo == NULL)
80 return;
82 /* BufReplace will eventually call SaveUndoInformation. This is mostly
83 good because it makes accumulating redo operations easier, however
84 SaveUndoInformation needs to know that it is being called in the context
85 of an undo. The inUndo field in the undo record indicates that this
86 record is in the process of being undone. */
87 undo->inUndo = True;
89 /* use the saved undo information to reverse changes */
90 BufReplace(window->buffer, undo->startPos, undo->endPos,
91 (undo->oldText != NULL ? undo->oldText : ""));
93 restoredTextLength = undo->oldText != NULL ? strlen(undo->oldText) : 0;
94 /* position the cursor in the focus pane after the changed text
95 to show the user where the undo was done */
96 TextSetCursorPos(window->lastFocus, undo->startPos + restoredTextLength);
98 if (GetPrefUndoModifiesSelection()) {
99 if (restoredTextLength > 0) {
100 BufSelect(window->buffer, undo->startPos, undo->startPos + restoredTextLength);
102 else {
103 BufUnselect(window->buffer);
106 MakeSelectionVisible(window, window->lastFocus);
108 /* restore the file's unmodified status if the file was unmodified
109 when the change being undone was originally made. Also, remove
110 the backup file, since the text in the buffer is now identical to
111 the original file */
112 if (undo->restoresToSaved) {
113 SetWindowModified(window, False);
114 RemoveBackupFile(window);
117 /* free the undo record and remove it from the chain */
118 removeUndoItem(window);
121 void Redo(WindowInfo *window)
123 UndoInfo *redo = window->redo;
124 int restoredTextLength;
126 /* return if nothing to redo */
127 if (window->redo == NULL)
128 return;
130 /* BufReplace will eventually call SaveUndoInformation. To indicate
131 to SaveUndoInformation that this is the context of a redo operation,
132 we set the inUndo indicator in the redo record */
133 redo->inUndo = True;
135 /* use the saved redo information to reverse changes */
136 BufReplace(window->buffer, redo->startPos, redo->endPos,
137 (redo->oldText != NULL ? redo->oldText : ""));
139 restoredTextLength = redo->oldText != NULL ? strlen(redo->oldText) : 0;
140 /* position the cursor in the focus pane after the changed text
141 to show the user where the redo was done */
142 TextSetCursorPos(window->lastFocus, redo->startPos + restoredTextLength);
144 if (GetPrefUndoModifiesSelection()) {
145 if (restoredTextLength > 0) {
146 BufSelect(window->buffer, redo->startPos, redo->startPos + restoredTextLength);
148 else {
149 BufUnselect(window->buffer);
152 MakeSelectionVisible(window, window->lastFocus);
154 /* restore the file's unmodified status if the file was unmodified
155 when the change being redone was originally made */
156 if (redo->restoresToSaved)
157 SetWindowModified(window, False);
159 /* remove the redo record from the chain and free it */
160 removeRedoItem(window);
165 ** SaveUndoInformation stores away the changes made to the text buffer. As a
166 ** side effect, it also increments the autoSave operation and character counts
167 ** since it needs to do the classification anyhow.
169 /* Note: This routine must be kept efficient. It is called for every character
170 typed. */
171 void SaveUndoInformation(WindowInfo *window, int pos, int nInserted,
172 int nDeleted, const char *deletedText)
174 int newType, oldType;
175 UndoInfo *u, *undo = window->undo;
176 int isUndo = (undo != NULL && undo->inUndo);
177 int isRedo = (window->redo != NULL && window->redo->inUndo);
179 /* redo operations become invalid once the user begins typing or does
180 other editing. If this is not a redo or undo operation and a redo
181 list still exists, clear it and dim the redo menu item */
182 if (!(isUndo || isRedo) && window->redo != NULL)
183 ClearRedoList(window);
185 /* figure out what kind of editing operation this is, and recall
186 what the last one was */
187 newType = determineUndoType(nInserted, nDeleted);
188 if (newType == UNDO_NOOP)
189 return;
190 oldType = (undo == NULL || isUndo) ? UNDO_NOOP : undo->type;
193 ** Check for continuations of single character operations. These are
194 ** accumulated so a whole insertion or deletion can be undone, rather
195 ** than just the last character that the user typed. If the window
196 ** is currently in an unmodified state, don't accumulate operations
197 ** across the save, so the user can undo back to the unmodified state.
199 if (window->fileChanged) {
201 /* normal sequential character insertion */
202 if ( ((oldType == ONE_CHAR_INSERT || oldType == ONE_CHAR_REPLACE)
203 && newType == ONE_CHAR_INSERT) && (pos == undo->endPos)) {
204 undo->endPos++;
205 window->autoSaveCharCount++;
206 return;
209 /* overstrike mode replacement */
210 if ((oldType == ONE_CHAR_REPLACE && newType == ONE_CHAR_REPLACE) &&
211 (pos == undo->endPos)) {
212 appendDeletedText(window, deletedText, nDeleted, FORWARD);
213 undo->endPos++;
214 window->autoSaveCharCount++;
215 return;
218 /* forward delete */
219 if ((oldType==ONE_CHAR_DELETE && newType==ONE_CHAR_DELETE) &&
220 (pos==undo->startPos)) {
221 appendDeletedText(window, deletedText, nDeleted, FORWARD);
222 return;
225 /* reverse delete */
226 if ((oldType==ONE_CHAR_DELETE && newType==ONE_CHAR_DELETE) &&
227 (pos == undo->startPos-1)) {
228 appendDeletedText(window, deletedText, nDeleted, REVERSE);
229 undo->startPos--;
230 undo->endPos--;
231 return;
236 ** The user has started a new operation, create a new undo record
237 ** and save the new undo data.
239 undo = (UndoInfo *)XtMalloc(sizeof(UndoInfo));
240 undo->oldLen = 0;
241 undo->oldText = NULL;
242 undo->type = newType;
243 undo->inUndo = False;
244 undo->restoresToSaved = False;
245 undo->startPos = pos;
246 undo->endPos = pos + nInserted;
248 /* if text was deleted, save it */
249 if (nDeleted > 0) {
250 undo->oldLen = nDeleted + 1; /* +1 is for null at end */
251 undo->oldText = XtMalloc(nDeleted + 1);
252 strcpy(undo->oldText, deletedText);
255 /* increment the operation count for the autosave feature */
256 window->autoSaveOpCount++;
258 /* if the window is currently unmodified, remove the previous
259 restoresToSaved marker, and set it on this record */
260 if (!window->fileChanged) {
261 undo->restoresToSaved = True;
262 for (u=window->undo; u!=NULL; u=u->next)
263 u->restoresToSaved = False;
264 for (u=window->redo; u!=NULL; u=u->next)
265 u->restoresToSaved = False;
268 /* Add the new record to the undo list unless SaveUndoInfo is
269 saving information generated by an Undo operation itself, in
270 which case, add the new record to the redo list. */
271 if (isUndo)
272 addRedoItem(window, undo);
273 else
274 addUndoItem(window, undo);
278 ** ClearUndoList, ClearRedoList
280 ** Functions for clearing all of the information off of the undo or redo
281 ** lists and adjusting the edit menu accordingly
283 void ClearUndoList(WindowInfo *window)
285 while (window->undo != NULL)
286 removeUndoItem(window);
288 void ClearRedoList(WindowInfo *window)
290 while (window->redo != NULL)
291 removeRedoItem(window);
295 ** DisableUnmodified
297 ** Remove the ability of a window to become "Unmodified" through a sequence
298 ** of undos. This can happen if the file the window is editing gets deleted,
299 ** for example.
301 void DisableUnmodified(WindowInfo *window)
303 UndoInfo *undo = window->undo;
305 while (undo != NULL){
306 undo->restoresToSaved = False;
307 undo = undo->next;
312 ** Add an undo record (already allocated by the caller) to the window's undo
313 ** list if the item pushes the undo operation or character counts past the
314 ** limits, trim the undo list to an acceptable length.
316 static void addUndoItem(WindowInfo *window, UndoInfo *undo)
319 /* Make the undo menu item sensitive now that there's something to undo */
320 if (window->undo == NULL) {
321 XtSetSensitive(window->undoItem, True);
322 SetBGMenuUndoSensitivity(window, True);
325 /* Add the item to the beginning of the list */
326 undo->next = window->undo;
327 window->undo = undo;
329 /* Increment the operation and memory counts */
330 window->undoOpCount++;
331 window->undoMemUsed += undo->oldLen;
333 /* Trim the list if it exceeds any of the limits */
334 if (window->undoOpCount > UNDO_OP_LIMIT)
335 trimUndoList(window, UNDO_OP_TRIMTO);
336 if (window->undoMemUsed > UNDO_WORRY_LIMIT)
337 trimUndoList(window, UNDO_WORRY_TRIMTO);
338 if (window->undoMemUsed > UNDO_PURGE_LIMIT)
339 trimUndoList(window, UNDO_PURGE_TRIMTO);
343 ** Add an item (already allocated by the caller) to the window's redo list.
345 static void addRedoItem(WindowInfo *window, UndoInfo *redo)
347 /* Make the redo menu item sensitive now that there's something to redo */
348 if (window->redo == NULL) {
349 XtSetSensitive(window->redoItem, True);
350 SetBGMenuRedoSensitivity(window, True);
353 /* Add the item to the beginning of the list */
354 redo->next = window->redo;
355 window->redo = redo;
359 ** Pop (remove and free) the current (front) undo record from the undo list
361 static void removeUndoItem(WindowInfo *window)
363 UndoInfo *undo = window->undo;
365 if (undo == NULL)
366 return;
368 /* Decrement the operation and memory counts */
369 window->undoOpCount--;
370 window->undoMemUsed -= undo->oldLen;
372 /* Remove and free the item */
373 window->undo = undo->next;
374 freeUndoRecord(undo);
376 /* if there are no more undo records left, dim the Undo menu item */
377 if (window->undo == NULL) {
378 XtSetSensitive(window->undoItem, False);
379 SetBGMenuUndoSensitivity(window, False);
384 ** Pop (remove and free) the current (front) redo record from the redo list
386 static void removeRedoItem(WindowInfo *window)
388 UndoInfo *redo = window->redo;
390 /* Remove and free the item */
391 window->redo = redo->next;
392 freeUndoRecord(redo);
394 /* if there are no more redo records left, dim the Redo menu item */
395 if (window->redo == NULL) {
396 XtSetSensitive(window->redoItem, False);
397 SetBGMenuRedoSensitivity(window, False);
402 ** Add deleted text to the beginning or end
403 ** of the text saved for undoing the last operation. This routine is intended
404 ** for continuing of a string of one character deletes or replaces, but will
405 ** work with more than one character.
407 static void appendDeletedText(WindowInfo *window, const char *deletedText,
408 int deletedLen, int direction)
410 UndoInfo *undo = window->undo;
411 char *comboText;
413 /* re-allocate, adding space for the new character(s) */
414 comboText = XtMalloc(undo->oldLen + deletedLen);
416 /* copy the new character and the already deleted text to the new memory */
417 if (direction == FORWARD) {
418 strcpy(comboText, undo->oldText);
419 strcat(comboText, deletedText);
420 } else {
421 strcpy(comboText, deletedText);
422 strcat(comboText, undo->oldText);
425 /* keep track of the additional memory now used by the undo list */
426 window->undoMemUsed++;
428 /* free the old saved text and attach the new */
429 XtFree(undo->oldText);
430 undo->oldText = comboText;
431 undo->oldLen += deletedLen;
435 ** Trim records off of the END of the undo list to reduce it to length
436 ** maxLength
438 static void trimUndoList(WindowInfo *window, int maxLength)
440 int i;
441 UndoInfo *u, *lastRec;
443 if (window->undo == NULL)
444 return;
446 /* Find last item on the list to leave intact */
447 for (i=1, u=window->undo; i<maxLength && u!=NULL; i++, u=u->next);
448 if (u == NULL)
449 return;
451 /* Trim off all subsequent entries */
452 lastRec = u;
453 while (lastRec->next != NULL) {
454 u = lastRec->next;
455 lastRec->next = u->next;
456 window->undoOpCount--;
457 window->undoMemUsed -= u->oldLen;
458 freeUndoRecord(u);
462 static int determineUndoType(int nInserted, int nDeleted)
464 int textDeleted, textInserted;
466 textDeleted = (nDeleted > 0);
467 textInserted = (nInserted > 0);
469 if (textInserted && !textDeleted) {
470 /* Insert */
471 if (nInserted == 1)
472 return ONE_CHAR_INSERT;
473 else
474 return BLOCK_INSERT;
475 } else if (textInserted && textDeleted) {
476 /* Replace */
477 if (nInserted == 1)
478 return ONE_CHAR_REPLACE;
479 else
480 return BLOCK_REPLACE;
481 } else if (!textInserted && textDeleted) {
482 /* Delete */
483 if (nDeleted == 1)
484 return ONE_CHAR_DELETE;
485 else
486 return BLOCK_DELETE;
487 } else {
488 /* Nothing deleted or inserted */
489 return UNDO_NOOP;
493 static void freeUndoRecord(UndoInfo *undo)
495 if (undo == NULL)
496 return;
498 XtFree(undo->oldText);
499 XtFree((char *)undo);