Fix our use of $em_tab_dist after it was changed to 0 for 'turned of'.
[nedit.git] / source / undo.c
blob2ddf0c8256fcea30873a9475f61806564d4eac46
1 static const char CVSID[] = "$Id: undo.c,v 1.19 2008/01/04 22:11:05 yooden 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. In addition, you may distribute version of this program linked to *
12 * Motif or Open Motif. See README for details. *
13 * *
14 * This software is distributed in the hope that it will be useful, but WITHOUT *
15 * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or *
16 * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License *
17 * for more details. *
18 * *
19 * You should have received a copy of the GNU General Public License along with *
20 * software; if not, write to the Free Software Foundation, Inc., 59 Temple *
21 * Place, Suite 330, Boston, MA 02111-1307 USA *
22 * *
23 * Nirvana Text Editor *
24 * May 10, 1991 *
25 * *
26 * Written by Mark Edel *
27 * *
28 *******************************************************************************/
30 #ifdef HAVE_CONFIG_H
31 #include "../config.h"
32 #endif
34 #include "undo.h"
35 #include "textBuf.h"
36 #include "text.h"
37 #include "nedit.h"
38 #include "search.h"
39 #include "window.h"
40 #include "file.h"
41 #include "userCmds.h"
42 #include "preferences.h"
44 #include <string.h>
45 #ifdef VMS
46 #include "../util/VMSparam.h"
47 #else
48 #ifndef __MVS__
49 #include <sys/param.h>
50 #endif
51 #endif /*VMS*/
53 #include <Xm/Xm.h>
54 #include <Xm/Text.h>
56 #ifdef HAVE_DEBUG_H
57 #include "../debug.h"
58 #endif
61 #define FORWARD 1
62 #define REVERSE 2
64 static void addUndoItem(WindowInfo *window, UndoInfo *undo);
65 static void addRedoItem(WindowInfo *window, UndoInfo *redo);
66 static void removeUndoItem(WindowInfo *window);
67 static void removeRedoItem(WindowInfo *window);
68 static void appendDeletedText(WindowInfo *window, const char *deletedText,
69 int deletedLen, int direction);
70 static void trimUndoList(WindowInfo *window, int maxLength);
71 static int determineUndoType(int nInserted, int nDeleted);
72 static void freeUndoRecord(UndoInfo *undo);
74 void Undo(WindowInfo *window)
76 UndoInfo *undo = window->undo;
77 int restoredTextLength;
79 /* return if nothing to undo */
80 if (undo == NULL)
81 return;
83 /* BufReplace will eventually call SaveUndoInformation. This is mostly
84 good because it makes accumulating redo operations easier, however
85 SaveUndoInformation needs to know that it is being called in the context
86 of an undo. The inUndo field in the undo record indicates that this
87 record is in the process of being undone. */
88 undo->inUndo = True;
90 /* use the saved undo information to reverse changes */
91 BufReplace(window->buffer, undo->startPos, undo->endPos,
92 (undo->oldText != NULL ? undo->oldText : ""));
94 restoredTextLength = undo->oldText != NULL ? strlen(undo->oldText) : 0;
95 if (!window->buffer->primary.selected || GetPrefUndoModifiesSelection()) {
96 /* position the cursor in the focus pane after the changed text
97 to show the user where the undo was done */
98 TextSetCursorPos(window->lastFocus, undo->startPos +
99 restoredTextLength);
102 if (GetPrefUndoModifiesSelection()) {
103 if (restoredTextLength > 0) {
104 BufSelect(window->buffer, undo->startPos, undo->startPos +
105 restoredTextLength);
107 else {
108 BufUnselect(window->buffer);
111 MakeSelectionVisible(window, window->lastFocus);
113 /* restore the file's unmodified status if the file was unmodified
114 when the change being undone was originally made. Also, remove
115 the backup file, since the text in the buffer is now identical to
116 the original file */
117 if (undo->restoresToSaved) {
118 SetWindowModified(window, False);
119 RemoveBackupFile(window);
122 /* free the undo record and remove it from the chain */
123 removeUndoItem(window);
126 void Redo(WindowInfo *window)
128 UndoInfo *redo = window->redo;
129 int restoredTextLength;
131 /* return if nothing to redo */
132 if (window->redo == NULL)
133 return;
135 /* BufReplace will eventually call SaveUndoInformation. To indicate
136 to SaveUndoInformation that this is the context of a redo operation,
137 we set the inUndo indicator in the redo record */
138 redo->inUndo = True;
140 /* use the saved redo information to reverse changes */
141 BufReplace(window->buffer, redo->startPos, redo->endPos,
142 (redo->oldText != NULL ? redo->oldText : ""));
144 restoredTextLength = redo->oldText != NULL ? strlen(redo->oldText) : 0;
145 if (!window->buffer->primary.selected || GetPrefUndoModifiesSelection()) {
146 /* position the cursor in the focus pane after the changed text
147 to show the user where the undo was done */
148 TextSetCursorPos(window->lastFocus, redo->startPos +
149 restoredTextLength);
151 if (GetPrefUndoModifiesSelection()) {
153 if (restoredTextLength > 0) {
154 BufSelect(window->buffer, redo->startPos, redo->startPos +
155 restoredTextLength);
157 else {
158 BufUnselect(window->buffer);
161 MakeSelectionVisible(window, window->lastFocus);
163 /* restore the file's unmodified status if the file was unmodified
164 when the change being redone was originally made. Also, remove
165 the backup file, since the text in the buffer is now identical to
166 the original file */
167 if (redo->restoresToSaved) {
168 SetWindowModified(window, False);
169 RemoveBackupFile(window);
172 /* remove the redo record from the chain and free it */
173 removeRedoItem(window);
178 ** SaveUndoInformation stores away the changes made to the text buffer. As a
179 ** side effect, it also increments the autoSave operation and character counts
180 ** since it needs to do the classification anyhow.
182 ** Note: This routine must be kept efficient. It is called for every
183 ** character typed.
185 void SaveUndoInformation(WindowInfo *window, int pos, int nInserted,
186 int nDeleted, const char *deletedText)
188 int newType, oldType;
189 UndoInfo *u, *undo = window->undo;
190 int isUndo = (undo != NULL && undo->inUndo);
191 int isRedo = (window->redo != NULL && window->redo->inUndo);
193 /* redo operations become invalid once the user begins typing or does
194 other editing. If this is not a redo or undo operation and a redo
195 list still exists, clear it and dim the redo menu item */
196 if (!(isUndo || isRedo) && window->redo != NULL)
197 ClearRedoList(window);
199 /* figure out what kind of editing operation this is, and recall
200 what the last one was */
201 newType = determineUndoType(nInserted, nDeleted);
202 if (newType == UNDO_NOOP)
203 return;
204 oldType = (undo == NULL || isUndo) ? UNDO_NOOP : undo->type;
207 ** Check for continuations of single character operations. These are
208 ** accumulated so a whole insertion or deletion can be undone, rather
209 ** than just the last character that the user typed. If the window
210 ** is currently in an unmodified state, don't accumulate operations
211 ** across the save, so the user can undo back to the unmodified state.
213 if (window->fileChanged) {
215 /* normal sequential character insertion */
216 if ( ((oldType == ONE_CHAR_INSERT || oldType == ONE_CHAR_REPLACE)
217 && newType == ONE_CHAR_INSERT) && (pos == undo->endPos)) {
218 undo->endPos++;
219 window->autoSaveCharCount++;
220 return;
223 /* overstrike mode replacement */
224 if ((oldType == ONE_CHAR_REPLACE && newType == ONE_CHAR_REPLACE) &&
225 (pos == undo->endPos)) {
226 appendDeletedText(window, deletedText, nDeleted, FORWARD);
227 undo->endPos++;
228 window->autoSaveCharCount++;
229 return;
232 /* forward delete */
233 if ((oldType==ONE_CHAR_DELETE && newType==ONE_CHAR_DELETE) &&
234 (pos==undo->startPos)) {
235 appendDeletedText(window, deletedText, nDeleted, FORWARD);
236 return;
239 /* reverse delete */
240 if ((oldType==ONE_CHAR_DELETE && newType==ONE_CHAR_DELETE) &&
241 (pos == undo->startPos-1)) {
242 appendDeletedText(window, deletedText, nDeleted, REVERSE);
243 undo->startPos--;
244 undo->endPos--;
245 return;
250 ** The user has started a new operation, create a new undo record
251 ** and save the new undo data.
253 undo = (UndoInfo *)XtMalloc(sizeof(UndoInfo));
254 undo->oldLen = 0;
255 undo->oldText = NULL;
256 undo->type = newType;
257 undo->inUndo = False;
258 undo->restoresToSaved = False;
259 undo->startPos = pos;
260 undo->endPos = pos + nInserted;
262 /* if text was deleted, save it */
263 if (nDeleted > 0) {
264 undo->oldLen = nDeleted + 1; /* +1 is for null at end */
265 undo->oldText = XtMalloc(nDeleted + 1);
266 strcpy(undo->oldText, deletedText);
269 /* increment the operation count for the autosave feature */
270 window->autoSaveOpCount++;
272 /* if the window is currently unmodified, remove the previous
273 restoresToSaved marker, and set it on this record */
274 if (!window->fileChanged) {
275 undo->restoresToSaved = True;
276 for (u=window->undo; u!=NULL; u=u->next)
277 u->restoresToSaved = False;
278 for (u=window->redo; u!=NULL; u=u->next)
279 u->restoresToSaved = False;
282 /* Add the new record to the undo list unless SaveUndoInfo is
283 saving information generated by an Undo operation itself, in
284 which case, add the new record to the redo list. */
285 if (isUndo)
286 addRedoItem(window, undo);
287 else
288 addUndoItem(window, undo);
292 ** ClearUndoList, ClearRedoList
294 ** Functions for clearing all of the information off of the undo or redo
295 ** lists and adjusting the edit menu accordingly
297 void ClearUndoList(WindowInfo *window)
299 while (window->undo != NULL)
300 removeUndoItem(window);
302 void ClearRedoList(WindowInfo *window)
304 while (window->redo != NULL)
305 removeRedoItem(window);
309 ** Add an undo record (already allocated by the caller) to the window's undo
310 ** list if the item pushes the undo operation or character counts past the
311 ** limits, trim the undo list to an acceptable length.
313 static void addUndoItem(WindowInfo *window, UndoInfo *undo)
316 /* Make the undo menu item sensitive now that there's something to undo */
317 if (window->undo == NULL) {
318 SetSensitive(window, window->undoItem, True);
319 SetBGMenuUndoSensitivity(window, True);
322 /* Add the item to the beginning of the list */
323 undo->next = window->undo;
324 window->undo = undo;
326 /* Increment the operation and memory counts */
327 window->undoOpCount++;
328 window->undoMemUsed += undo->oldLen;
330 /* Trim the list if it exceeds any of the limits */
331 if (window->undoOpCount > UNDO_OP_LIMIT)
332 trimUndoList(window, UNDO_OP_TRIMTO);
333 if (window->undoMemUsed > UNDO_WORRY_LIMIT)
334 trimUndoList(window, UNDO_WORRY_TRIMTO);
335 if (window->undoMemUsed > UNDO_PURGE_LIMIT)
336 trimUndoList(window, UNDO_PURGE_TRIMTO);
340 ** Add an item (already allocated by the caller) to the window's redo list.
342 static void addRedoItem(WindowInfo *window, UndoInfo *redo)
344 /* Make the redo menu item sensitive now that there's something to redo */
345 if (window->redo == NULL) {
346 SetSensitive(window, window->redoItem, True);
347 SetBGMenuRedoSensitivity(window, True);
350 /* Add the item to the beginning of the list */
351 redo->next = window->redo;
352 window->redo = redo;
356 ** Pop (remove and free) the current (front) undo record from the undo list
358 static void removeUndoItem(WindowInfo *window)
360 UndoInfo *undo = window->undo;
362 if (undo == NULL)
363 return;
365 /* Decrement the operation and memory counts */
366 window->undoOpCount--;
367 window->undoMemUsed -= undo->oldLen;
369 /* Remove and free the item */
370 window->undo = undo->next;
371 freeUndoRecord(undo);
373 /* if there are no more undo records left, dim the Undo menu item */
374 if (window->undo == NULL) {
375 SetSensitive(window, window->undoItem, False);
376 SetBGMenuUndoSensitivity(window, False);
381 ** Pop (remove and free) the current (front) redo record from the redo list
383 static void removeRedoItem(WindowInfo *window)
385 UndoInfo *redo = window->redo;
387 /* Remove and free the item */
388 window->redo = redo->next;
389 freeUndoRecord(redo);
391 /* if there are no more redo records left, dim the Redo menu item */
392 if (window->redo == NULL) {
393 SetSensitive(window, window->redoItem, False);
394 SetBGMenuRedoSensitivity(window, False);
399 ** Add deleted text to the beginning or end
400 ** of the text saved for undoing the last operation. This routine is intended
401 ** for continuing of a string of one character deletes or replaces, but will
402 ** work with more than one character.
404 static void appendDeletedText(WindowInfo *window, const char *deletedText,
405 int deletedLen, int direction)
407 UndoInfo *undo = window->undo;
408 char *comboText;
410 /* re-allocate, adding space for the new character(s) */
411 comboText = XtMalloc(undo->oldLen + deletedLen);
413 /* copy the new character and the already deleted text to the new memory */
414 if (direction == FORWARD) {
415 strcpy(comboText, undo->oldText);
416 strcat(comboText, deletedText);
417 } else {
418 strcpy(comboText, deletedText);
419 strcat(comboText, undo->oldText);
422 /* keep track of the additional memory now used by the undo list */
423 window->undoMemUsed++;
425 /* free the old saved text and attach the new */
426 XtFree(undo->oldText);
427 undo->oldText = comboText;
428 undo->oldLen += deletedLen;
432 ** Trim records off of the END of the undo list to reduce it to length
433 ** maxLength
435 static void trimUndoList(WindowInfo *window, int maxLength)
437 int i;
438 UndoInfo *u, *lastRec;
440 if (window->undo == NULL)
441 return;
443 /* Find last item on the list to leave intact */
444 for (i=1, u=window->undo; i<maxLength && u!=NULL; i++, u=u->next);
445 if (u == NULL)
446 return;
448 /* Trim off all subsequent entries */
449 lastRec = u;
450 while (lastRec->next != NULL) {
451 u = lastRec->next;
452 lastRec->next = u->next;
453 window->undoOpCount--;
454 window->undoMemUsed -= u->oldLen;
455 freeUndoRecord(u);
459 static int determineUndoType(int nInserted, int nDeleted)
461 int textDeleted, textInserted;
463 textDeleted = (nDeleted > 0);
464 textInserted = (nInserted > 0);
466 if (textInserted && !textDeleted) {
467 /* Insert */
468 if (nInserted == 1)
469 return ONE_CHAR_INSERT;
470 else
471 return BLOCK_INSERT;
472 } else if (textInserted && textDeleted) {
473 /* Replace */
474 if (nInserted == 1)
475 return ONE_CHAR_REPLACE;
476 else
477 return BLOCK_REPLACE;
478 } else if (!textInserted && textDeleted) {
479 /* Delete */
480 if (nDeleted == 1)
481 return ONE_CHAR_DELETE;
482 else
483 return BLOCK_DELETE;
484 } else {
485 /* Nothing deleted or inserted */
486 return UNDO_NOOP;
490 static void freeUndoRecord(UndoInfo *undo)
492 if (undo == NULL)
493 return;
495 XtFree(undo->oldText);
496 XtFree((char *)undo);