Committed SF patch #963120: Open tab in window on current desktop.
[nedit.git] / source / undo.c
blob7fa32bc560f27753b56b671ebcf7bad9040b434b
1 static const char CVSID[] = "$Id: undo.c,v 1.16 2004/03/02 12:47:22 tksoh 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 if (!window->buffer->primary.selected || GetPrefUndoModifiesSelection()) {
95 /* position the cursor in the focus pane after the changed text
96 to show the user where the undo was done */
97 TextSetCursorPos(window->lastFocus, undo->startPos +
98 restoredTextLength);
101 if (GetPrefUndoModifiesSelection()) {
102 if (restoredTextLength > 0) {
103 BufSelect(window->buffer, undo->startPos, undo->startPos +
104 restoredTextLength);
106 else {
107 BufUnselect(window->buffer);
110 MakeSelectionVisible(window, window->lastFocus);
112 /* restore the file's unmodified status if the file was unmodified
113 when the change being undone was originally made. Also, remove
114 the backup file, since the text in the buffer is now identical to
115 the original file */
116 if (undo->restoresToSaved) {
117 SetWindowModified(window, False);
118 RemoveBackupFile(window);
121 /* free the undo record and remove it from the chain */
122 removeUndoItem(window);
125 void Redo(WindowInfo *window)
127 UndoInfo *redo = window->redo;
128 int restoredTextLength;
130 /* return if nothing to redo */
131 if (window->redo == NULL)
132 return;
134 /* BufReplace will eventually call SaveUndoInformation. To indicate
135 to SaveUndoInformation that this is the context of a redo operation,
136 we set the inUndo indicator in the redo record */
137 redo->inUndo = True;
139 /* use the saved redo information to reverse changes */
140 BufReplace(window->buffer, redo->startPos, redo->endPos,
141 (redo->oldText != NULL ? redo->oldText : ""));
143 restoredTextLength = redo->oldText != NULL ? strlen(redo->oldText) : 0;
144 if (!window->buffer->primary.selected || GetPrefUndoModifiesSelection()) {
145 /* position the cursor in the focus pane after the changed text
146 to show the user where the undo was done */
147 TextSetCursorPos(window->lastFocus, redo->startPos +
148 restoredTextLength);
150 if (GetPrefUndoModifiesSelection()) {
152 if (restoredTextLength > 0) {
153 BufSelect(window->buffer, redo->startPos, redo->startPos +
154 restoredTextLength);
156 else {
157 BufUnselect(window->buffer);
160 MakeSelectionVisible(window, window->lastFocus);
162 /* restore the file's unmodified status if the file was unmodified
163 when the change being redone was originally made */
164 if (redo->restoresToSaved)
165 SetWindowModified(window, False);
167 /* remove the redo record from the chain and free it */
168 removeRedoItem(window);
173 ** SaveUndoInformation stores away the changes made to the text buffer. As a
174 ** side effect, it also increments the autoSave operation and character counts
175 ** since it needs to do the classification anyhow.
177 ** Note: This routine must be kept efficient. It is called for every
178 ** character typed.
180 void SaveUndoInformation(WindowInfo *window, int pos, int nInserted,
181 int nDeleted, const char *deletedText)
183 int newType, oldType;
184 UndoInfo *u, *undo = window->undo;
185 int isUndo = (undo != NULL && undo->inUndo);
186 int isRedo = (window->redo != NULL && window->redo->inUndo);
188 /* redo operations become invalid once the user begins typing or does
189 other editing. If this is not a redo or undo operation and a redo
190 list still exists, clear it and dim the redo menu item */
191 if (!(isUndo || isRedo) && window->redo != NULL)
192 ClearRedoList(window);
194 /* figure out what kind of editing operation this is, and recall
195 what the last one was */
196 newType = determineUndoType(nInserted, nDeleted);
197 if (newType == UNDO_NOOP)
198 return;
199 oldType = (undo == NULL || isUndo) ? UNDO_NOOP : undo->type;
202 ** Check for continuations of single character operations. These are
203 ** accumulated so a whole insertion or deletion can be undone, rather
204 ** than just the last character that the user typed. If the window
205 ** is currently in an unmodified state, don't accumulate operations
206 ** across the save, so the user can undo back to the unmodified state.
208 if (window->fileChanged) {
210 /* normal sequential character insertion */
211 if ( ((oldType == ONE_CHAR_INSERT || oldType == ONE_CHAR_REPLACE)
212 && newType == ONE_CHAR_INSERT) && (pos == undo->endPos)) {
213 undo->endPos++;
214 window->autoSaveCharCount++;
215 return;
218 /* overstrike mode replacement */
219 if ((oldType == ONE_CHAR_REPLACE && newType == ONE_CHAR_REPLACE) &&
220 (pos == undo->endPos)) {
221 appendDeletedText(window, deletedText, nDeleted, FORWARD);
222 undo->endPos++;
223 window->autoSaveCharCount++;
224 return;
227 /* forward delete */
228 if ((oldType==ONE_CHAR_DELETE && newType==ONE_CHAR_DELETE) &&
229 (pos==undo->startPos)) {
230 appendDeletedText(window, deletedText, nDeleted, FORWARD);
231 return;
234 /* reverse delete */
235 if ((oldType==ONE_CHAR_DELETE && newType==ONE_CHAR_DELETE) &&
236 (pos == undo->startPos-1)) {
237 appendDeletedText(window, deletedText, nDeleted, REVERSE);
238 undo->startPos--;
239 undo->endPos--;
240 return;
245 ** The user has started a new operation, create a new undo record
246 ** and save the new undo data.
248 undo = (UndoInfo *)XtMalloc(sizeof(UndoInfo));
249 undo->oldLen = 0;
250 undo->oldText = NULL;
251 undo->type = newType;
252 undo->inUndo = False;
253 undo->restoresToSaved = False;
254 undo->startPos = pos;
255 undo->endPos = pos + nInserted;
257 /* if text was deleted, save it */
258 if (nDeleted > 0) {
259 undo->oldLen = nDeleted + 1; /* +1 is for null at end */
260 undo->oldText = XtMalloc(nDeleted + 1);
261 strcpy(undo->oldText, deletedText);
264 /* increment the operation count for the autosave feature */
265 window->autoSaveOpCount++;
267 /* if the window is currently unmodified, remove the previous
268 restoresToSaved marker, and set it on this record */
269 if (!window->fileChanged) {
270 undo->restoresToSaved = True;
271 for (u=window->undo; u!=NULL; u=u->next)
272 u->restoresToSaved = False;
273 for (u=window->redo; u!=NULL; u=u->next)
274 u->restoresToSaved = False;
277 /* Add the new record to the undo list unless SaveUndoInfo is
278 saving information generated by an Undo operation itself, in
279 which case, add the new record to the redo list. */
280 if (isUndo)
281 addRedoItem(window, undo);
282 else
283 addUndoItem(window, undo);
287 ** ClearUndoList, ClearRedoList
289 ** Functions for clearing all of the information off of the undo or redo
290 ** lists and adjusting the edit menu accordingly
292 void ClearUndoList(WindowInfo *window)
294 while (window->undo != NULL)
295 removeUndoItem(window);
297 void ClearRedoList(WindowInfo *window)
299 while (window->redo != NULL)
300 removeRedoItem(window);
304 ** DisableUnmodified
306 ** Remove the ability of a window to become "Unmodified" through a sequence
307 ** of undos. This can happen if the file the window is editing gets deleted,
308 ** for example.
310 void DisableUnmodified(WindowInfo *window)
312 UndoInfo *undo = window->undo;
314 while (undo != NULL){
315 undo->restoresToSaved = False;
316 undo = undo->next;
321 ** Add an undo record (already allocated by the caller) to the window's undo
322 ** list if the item pushes the undo operation or character counts past the
323 ** limits, trim the undo list to an acceptable length.
325 static void addUndoItem(WindowInfo *window, UndoInfo *undo)
328 /* Make the undo menu item sensitive now that there's something to undo */
329 if (window->undo == NULL) {
330 SetSensitive(window, window->undoItem, True);
331 SetBGMenuUndoSensitivity(window, True);
334 /* Add the item to the beginning of the list */
335 undo->next = window->undo;
336 window->undo = undo;
338 /* Increment the operation and memory counts */
339 window->undoOpCount++;
340 window->undoMemUsed += undo->oldLen;
342 /* Trim the list if it exceeds any of the limits */
343 if (window->undoOpCount > UNDO_OP_LIMIT)
344 trimUndoList(window, UNDO_OP_TRIMTO);
345 if (window->undoMemUsed > UNDO_WORRY_LIMIT)
346 trimUndoList(window, UNDO_WORRY_TRIMTO);
347 if (window->undoMemUsed > UNDO_PURGE_LIMIT)
348 trimUndoList(window, UNDO_PURGE_TRIMTO);
352 ** Add an item (already allocated by the caller) to the window's redo list.
354 static void addRedoItem(WindowInfo *window, UndoInfo *redo)
356 /* Make the redo menu item sensitive now that there's something to redo */
357 if (window->redo == NULL) {
358 SetSensitive(window, window->redoItem, True);
359 SetBGMenuRedoSensitivity(window, True);
362 /* Add the item to the beginning of the list */
363 redo->next = window->redo;
364 window->redo = redo;
368 ** Pop (remove and free) the current (front) undo record from the undo list
370 static void removeUndoItem(WindowInfo *window)
372 UndoInfo *undo = window->undo;
374 if (undo == NULL)
375 return;
377 /* Decrement the operation and memory counts */
378 window->undoOpCount--;
379 window->undoMemUsed -= undo->oldLen;
381 /* Remove and free the item */
382 window->undo = undo->next;
383 freeUndoRecord(undo);
385 /* if there are no more undo records left, dim the Undo menu item */
386 if (window->undo == NULL) {
387 SetSensitive(window, window->undoItem, False);
388 SetBGMenuUndoSensitivity(window, False);
393 ** Pop (remove and free) the current (front) redo record from the redo list
395 static void removeRedoItem(WindowInfo *window)
397 UndoInfo *redo = window->redo;
399 /* Remove and free the item */
400 window->redo = redo->next;
401 freeUndoRecord(redo);
403 /* if there are no more redo records left, dim the Redo menu item */
404 if (window->redo == NULL) {
405 SetSensitive(window, window->redoItem, False);
406 SetBGMenuRedoSensitivity(window, False);
411 ** Add deleted text to the beginning or end
412 ** of the text saved for undoing the last operation. This routine is intended
413 ** for continuing of a string of one character deletes or replaces, but will
414 ** work with more than one character.
416 static void appendDeletedText(WindowInfo *window, const char *deletedText,
417 int deletedLen, int direction)
419 UndoInfo *undo = window->undo;
420 char *comboText;
422 /* re-allocate, adding space for the new character(s) */
423 comboText = XtMalloc(undo->oldLen + deletedLen);
425 /* copy the new character and the already deleted text to the new memory */
426 if (direction == FORWARD) {
427 strcpy(comboText, undo->oldText);
428 strcat(comboText, deletedText);
429 } else {
430 strcpy(comboText, deletedText);
431 strcat(comboText, undo->oldText);
434 /* keep track of the additional memory now used by the undo list */
435 window->undoMemUsed++;
437 /* free the old saved text and attach the new */
438 XtFree(undo->oldText);
439 undo->oldText = comboText;
440 undo->oldLen += deletedLen;
444 ** Trim records off of the END of the undo list to reduce it to length
445 ** maxLength
447 static void trimUndoList(WindowInfo *window, int maxLength)
449 int i;
450 UndoInfo *u, *lastRec;
452 if (window->undo == NULL)
453 return;
455 /* Find last item on the list to leave intact */
456 for (i=1, u=window->undo; i<maxLength && u!=NULL; i++, u=u->next);
457 if (u == NULL)
458 return;
460 /* Trim off all subsequent entries */
461 lastRec = u;
462 while (lastRec->next != NULL) {
463 u = lastRec->next;
464 lastRec->next = u->next;
465 window->undoOpCount--;
466 window->undoMemUsed -= u->oldLen;
467 freeUndoRecord(u);
471 static int determineUndoType(int nInserted, int nDeleted)
473 int textDeleted, textInserted;
475 textDeleted = (nDeleted > 0);
476 textInserted = (nInserted > 0);
478 if (textInserted && !textDeleted) {
479 /* Insert */
480 if (nInserted == 1)
481 return ONE_CHAR_INSERT;
482 else
483 return BLOCK_INSERT;
484 } else if (textInserted && textDeleted) {
485 /* Replace */
486 if (nInserted == 1)
487 return ONE_CHAR_REPLACE;
488 else
489 return BLOCK_REPLACE;
490 } else if (!textInserted && textDeleted) {
491 /* Delete */
492 if (nDeleted == 1)
493 return ONE_CHAR_DELETE;
494 else
495 return BLOCK_DELETE;
496 } else {
497 /* Nothing deleted or inserted */
498 return UNDO_NOOP;
502 static void freeUndoRecord(UndoInfo *undo)
504 if (undo == NULL)
505 return;
507 XtFree(undo->oldText);
508 XtFree((char *)undo);