Support for Lynx, MacOS; better x86 reporting
[nedit.git] / source / undo.c
bloba9e00422171e063ab92ed57b63e4a83f6dc7997c
1 static const char CVSID[] = "$Id: undo.c,v 1.11 2002/07/11 21:18:12 slobasso 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"
42 #include <string.h>
43 #ifdef VMS
44 #include "../util/VMSparam.h"
45 #else
46 #ifndef __MVS__
47 #include <sys/param.h>
48 #endif
49 #endif /*VMS*/
51 #include <Xm/Xm.h>
52 #include <Xm/Text.h>
54 #ifdef HAVE_DEBUG_H
55 #include "../debug.h"
56 #endif
59 #define FORWARD 1
60 #define REVERSE 2
62 static void addUndoItem(WindowInfo *window, UndoInfo *undo);
63 static void addRedoItem(WindowInfo *window, UndoInfo *redo);
64 static void removeUndoItem(WindowInfo *window);
65 static void removeRedoItem(WindowInfo *window);
66 static void appendDeletedText(WindowInfo *window, const char *deletedText,
67 int deletedLen, int direction);
68 static void trimUndoList(WindowInfo *window, int maxLength);
69 static int determineUndoType(int nInserted, int nDeleted);
70 static void freeUndoRecord(UndoInfo *undo);
72 void Undo(WindowInfo *window)
74 UndoInfo *undo = window->undo;
75 int restoredTextLength;
77 /* return if nothing to undo */
78 if (undo == NULL)
79 return;
81 /* BufReplace will eventually call SaveUndoInformation. This is mostly
82 good because it makes accumulating redo operations easier, however
83 SaveUndoInformation needs to know that it is being called in the context
84 of an undo. The inUndo field in the undo record indicates that this
85 record is in the process of being undone. */
86 undo->inUndo = True;
88 /* use the saved undo information to reverse changes */
89 BufReplace(window->buffer, undo->startPos, undo->endPos,
90 (undo->oldText != NULL ? undo->oldText : ""));
92 restoredTextLength = undo->oldText != NULL ? strlen(undo->oldText) : 0;
93 /* position the cursor in the focus pane after the changed text
94 to show the user where the undo was done */
95 TextSetCursorPos(window->lastFocus, undo->startPos + restoredTextLength);
97 if (restoredTextLength > 0) {
98 BufSelect(window->buffer, undo->startPos, undo->startPos + restoredTextLength);
100 else {
101 BufUnselect(window->buffer);
104 /* restore the file's unmodified status if the file was unmodified
105 when the change being undone was originally made. Also, remove
106 the backup file, since the text in the buffer is now identical to
107 the original file */
108 if (undo->restoresToSaved) {
109 SetWindowModified(window, False);
110 RemoveBackupFile(window);
113 /* free the undo record and remove it from the chain */
114 removeUndoItem(window);
117 void Redo(WindowInfo *window)
119 UndoInfo *redo = window->redo;
120 int restoredTextLength;
122 /* return if nothing to redo */
123 if (window->redo == NULL)
124 return;
126 /* BufReplace will eventually call SaveUndoInformation. To indicate
127 to SaveUndoInformation that this is the context of a redo operation,
128 we set the inUndo indicator in the redo record */
129 redo->inUndo = True;
131 /* use the saved redo information to reverse changes */
132 BufReplace(window->buffer, redo->startPos, redo->endPos,
133 (redo->oldText != NULL ? redo->oldText : ""));
135 restoredTextLength = redo->oldText != NULL ? strlen(redo->oldText) : 0;
136 /* position the cursor in the focus pane after the changed text
137 to show the user where the redo was done */
138 TextSetCursorPos(window->lastFocus, redo->startPos + restoredTextLength);
140 if (restoredTextLength > 0) {
141 BufSelect(window->buffer, redo->startPos, redo->startPos + restoredTextLength);
143 else {
144 BufUnselect(window->buffer);
147 /* restore the file's unmodified status if the file was unmodified
148 when the change being redone was originally made */
149 if (redo->restoresToSaved)
150 SetWindowModified(window, False);
152 /* remove the redo record from the chain and free it */
153 removeRedoItem(window);
158 ** SaveUndoInformation stores away the changes made to the text buffer. As a
159 ** side effect, it also increments the autoSave operation and character counts
160 ** since it needs to do the classification anyhow.
162 /* Note: This routine must be kept efficient. It is called for every character
163 typed. */
164 void SaveUndoInformation(WindowInfo *window, int pos, int nInserted,
165 int nDeleted, const char *deletedText)
167 int newType, oldType;
168 UndoInfo *u, *undo = window->undo;
169 int isUndo = (undo != NULL && undo->inUndo);
170 int isRedo = (window->redo != NULL && window->redo->inUndo);
172 /* redo operations become invalid once the user begins typing or does
173 other editing. If this is not a redo or undo operation and a redo
174 list still exists, clear it and dim the redo menu item */
175 if (!(isUndo || isRedo) && window->redo != NULL)
176 ClearRedoList(window);
178 /* figure out what kind of editing operation this is, and recall
179 what the last one was */
180 newType = determineUndoType(nInserted, nDeleted);
181 if (newType == UNDO_NOOP)
182 return;
183 oldType = (undo == NULL || isUndo) ? UNDO_NOOP : undo->type;
186 ** Check for continuations of single character operations. These are
187 ** accumulated so a whole insertion or deletion can be undone, rather
188 ** than just the last character that the user typed. If the window
189 ** is currently in an unmodified state, don't accumulate operations
190 ** across the save, so the user can undo back to the unmodified state.
192 if (window->fileChanged) {
194 /* normal sequential character insertion */
195 if ( ((oldType == ONE_CHAR_INSERT || oldType == ONE_CHAR_REPLACE)
196 && newType == ONE_CHAR_INSERT) && (pos == undo->endPos)) {
197 undo->endPos++;
198 window->autoSaveCharCount++;
199 return;
202 /* overstrike mode replacement */
203 if ((oldType == ONE_CHAR_REPLACE && newType == ONE_CHAR_REPLACE) &&
204 (pos == undo->endPos)) {
205 appendDeletedText(window, deletedText, nDeleted, FORWARD);
206 undo->endPos++;
207 window->autoSaveCharCount++;
208 return;
211 /* forward delete */
212 if ((oldType==ONE_CHAR_DELETE && newType==ONE_CHAR_DELETE) &&
213 (pos==undo->startPos)) {
214 appendDeletedText(window, deletedText, nDeleted, FORWARD);
215 return;
218 /* reverse delete */
219 if ((oldType==ONE_CHAR_DELETE && newType==ONE_CHAR_DELETE) &&
220 (pos == undo->startPos-1)) {
221 appendDeletedText(window, deletedText, nDeleted, REVERSE);
222 undo->startPos--;
223 undo->endPos--;
224 return;
229 ** The user has started a new operation, create a new undo record
230 ** and save the new undo data.
232 undo = (UndoInfo *)XtMalloc(sizeof(UndoInfo));
233 undo->oldLen = 0;
234 undo->oldText = NULL;
235 undo->type = newType;
236 undo->inUndo = False;
237 undo->restoresToSaved = False;
238 undo->startPos = pos;
239 undo->endPos = pos + nInserted;
241 /* if text was deleted, save it */
242 if (nDeleted > 0) {
243 undo->oldLen = nDeleted + 1; /* +1 is for null at end */
244 undo->oldText = XtMalloc(nDeleted + 1);
245 strcpy(undo->oldText, deletedText);
248 /* increment the operation count for the autosave feature */
249 window->autoSaveOpCount++;
251 /* if the window is currently unmodified, remove the previous
252 restoresToSaved marker, and set it on this record */
253 if (!window->fileChanged) {
254 undo->restoresToSaved = True;
255 for (u=window->undo; u!=NULL; u=u->next)
256 u->restoresToSaved = False;
257 for (u=window->redo; u!=NULL; u=u->next)
258 u->restoresToSaved = False;
261 /* Add the new record to the undo list unless SaveUndoInfo is
262 saving information generated by an Undo operation itself, in
263 which case, add the new record to the redo list. */
264 if (isUndo)
265 addRedoItem(window, undo);
266 else
267 addUndoItem(window, undo);
271 ** ClearUndoList, ClearRedoList
273 ** Functions for clearing all of the information off of the undo or redo
274 ** lists and adjusting the edit menu accordingly
276 void ClearUndoList(WindowInfo *window)
278 while (window->undo != NULL)
279 removeUndoItem(window);
281 void ClearRedoList(WindowInfo *window)
283 while (window->redo != NULL)
284 removeRedoItem(window);
288 ** Add an undo record (already allocated by the caller) to the window's undo
289 ** list if the item pushes the undo operation or character counts past the
290 ** limits, trim the undo list to an acceptable length.
292 static void addUndoItem(WindowInfo *window, UndoInfo *undo)
295 /* Make the undo menu item sensitive now that there's something to undo */
296 if (window->undo == NULL) {
297 XtSetSensitive(window->undoItem, True);
298 SetBGMenuUndoSensitivity(window, True);
301 /* Add the item to the beginning of the list */
302 undo->next = window->undo;
303 window->undo = undo;
305 /* Increment the operation and memory counts */
306 window->undoOpCount++;
307 window->undoMemUsed += undo->oldLen;
309 /* Trim the list if it exceeds any of the limits */
310 if (window->undoOpCount > UNDO_OP_LIMIT)
311 trimUndoList(window, UNDO_OP_TRIMTO);
312 if (window->undoMemUsed > UNDO_WORRY_LIMIT)
313 trimUndoList(window, UNDO_WORRY_TRIMTO);
314 if (window->undoMemUsed > UNDO_PURGE_LIMIT)
315 trimUndoList(window, UNDO_PURGE_TRIMTO);
319 ** Add an item (already allocated by the caller) to the window's redo list.
321 static void addRedoItem(WindowInfo *window, UndoInfo *redo)
323 /* Make the redo menu item sensitive now that there's something to redo */
324 if (window->redo == NULL) {
325 XtSetSensitive(window->redoItem, True);
326 SetBGMenuRedoSensitivity(window, True);
329 /* Add the item to the beginning of the list */
330 redo->next = window->redo;
331 window->redo = redo;
335 ** Pop (remove and free) the current (front) undo record from the undo list
337 static void removeUndoItem(WindowInfo *window)
339 UndoInfo *undo = window->undo;
341 if (undo == NULL)
342 return;
344 /* Decrement the operation and memory counts */
345 window->undoOpCount--;
346 window->undoMemUsed -= undo->oldLen;
348 /* Remove and free the item */
349 window->undo = undo->next;
350 freeUndoRecord(undo);
352 /* if there are no more undo records left, dim the Undo menu item */
353 if (window->undo == NULL) {
354 XtSetSensitive(window->undoItem, False);
355 SetBGMenuUndoSensitivity(window, False);
360 ** Pop (remove and free) the current (front) redo record from the redo list
362 static void removeRedoItem(WindowInfo *window)
364 UndoInfo *redo = window->redo;
366 /* Remove and free the item */
367 window->redo = redo->next;
368 freeUndoRecord(redo);
370 /* if there are no more redo records left, dim the Redo menu item */
371 if (window->redo == NULL) {
372 XtSetSensitive(window->redoItem, False);
373 SetBGMenuRedoSensitivity(window, False);
378 ** Add deleted text to the beginning or end
379 ** of the text saved for undoing the last operation. This routine is intended
380 ** for continuing of a string of one character deletes or replaces, but will
381 ** work with more than one character.
383 static void appendDeletedText(WindowInfo *window, const char *deletedText,
384 int deletedLen, int direction)
386 UndoInfo *undo = window->undo;
387 char *comboText;
389 /* re-allocate, adding space for the new character(s) */
390 comboText = XtMalloc(undo->oldLen + deletedLen);
392 /* copy the new character and the already deleted text to the new memory */
393 if (direction == FORWARD) {
394 strcpy(comboText, undo->oldText);
395 strcat(comboText, deletedText);
396 } else {
397 strcpy(comboText, deletedText);
398 strcat(comboText, undo->oldText);
401 /* keep track of the additional memory now used by the undo list */
402 window->undoMemUsed++;
404 /* free the old saved text and attach the new */
405 XtFree(undo->oldText);
406 undo->oldText = comboText;
407 undo->oldLen += deletedLen;
411 ** Trim records off of the END of the undo list to reduce it to length
412 ** maxLength
414 static void trimUndoList(WindowInfo *window, int maxLength)
416 int i;
417 UndoInfo *u, *lastRec;
419 if (window->undo == NULL)
420 return;
422 /* Find last item on the list to leave intact */
423 for (i=1, u=window->undo; i<maxLength && u!=NULL; i++, u=u->next);
424 if (u == NULL)
425 return;
427 /* Trim off all subsequent entries */
428 lastRec = u;
429 while (lastRec->next != NULL) {
430 u = lastRec->next;
431 lastRec->next = u->next;
432 window->undoOpCount--;
433 window->undoMemUsed -= u->oldLen;
434 freeUndoRecord(u);
438 static int determineUndoType(int nInserted, int nDeleted)
440 int textDeleted, textInserted;
442 textDeleted = (nDeleted > 0);
443 textInserted = (nInserted > 0);
445 if (textInserted && !textDeleted) {
446 /* Insert */
447 if (nInserted == 1)
448 return ONE_CHAR_INSERT;
449 else
450 return BLOCK_INSERT;
451 } else if (textInserted && textDeleted) {
452 /* Replace */
453 if (nInserted == 1)
454 return ONE_CHAR_REPLACE;
455 else
456 return BLOCK_REPLACE;
457 } else if (!textInserted && textDeleted) {
458 /* Delete */
459 if (nDeleted == 1)
460 return ONE_CHAR_DELETE;
461 else
462 return BLOCK_DELETE;
463 } else {
464 /* Nothing deleted or inserted */
465 return UNDO_NOOP;
469 static void freeUndoRecord(UndoInfo *undo)
471 if (undo == NULL)
472 return;
474 XtFree(undo->oldText);
475 XtFree((char *)undo);