UNIXworkcode

1 /******************************************************************************* 2 * * 3 * undo.c -- Nirvana Editor undo command * 4 * * 5 * Copyright (C) 1999 Mark Edel * 6 * * 7 * This is free software; you can redistribute it and/or modify it under the * 8 * terms of the GNU General Public License as published by the Free Software * 9 * Foundation; either version 2 of the License, or (at your option) any later * 10 * version. In addition, you may distribute version of this program linked to * 11 * Motif or Open Motif. See README for details. * 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 *******************************************************************************/ 28 29 #ifdef HAVE_CONFIG_H 30 #include "../config.h" 31 #endif 32 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" 42 #include "../util/nedit_malloc.h" 43 44 #include <string.h> 45 #include <sys/param.h> 46 47 #include <Xm/Xm.h> 48 #include <Xm/Text.h> 49 50 #ifdef HAVE_DEBUG_H 51 #include "../debug.h" 52 #endif 53 54 55 #define FORWARD 1 56 #define REVERSE 2 57 58 static void addUndoItem(WindowInfo *window, UndoInfo *undo); 59 static void addRedoItem(WindowInfo *window, UndoInfo *redo); 60 static void removeUndoItem(WindowInfo *window); 61 static void removeRedoItem(WindowInfo *window); 62 static void appendDeletedText(WindowInfo *window, const char *deletedText, 63 int deletedLen, int direction); 64 static void trimUndoList(WindowInfo *window, int maxLength); 65 static int determineUndoType(int nInserted, int nDeleted); 66 static void freeUndoRecord(UndoInfo *undo); 67 68 static void doUndo(WindowInfo *window, int isBatch, size_t *cursors, int cursorIndex) 69 { 70 UndoInfo *undo = window->undo; 71 int restoredTextLength; 72 73 /* return if nothing to undo */ 74 if (undo == NULL) 75 return; 76 77 /* BufReplace will eventually call SaveUndoInformation. This is mostly 78 good because it makes accumulating redo operations easier, however 79 SaveUndoInformation needs to know that it is being called in the context 80 of an undo. The inUndo field in the undo record indicates that this 81 record is in the process of being undone. */ 82 undo->inUndo = True; 83 84 /* use the saved undo information to reverse changes */ 85 BufReplace(window->buffer, undo->startPos, undo->endPos, 86 (undo->oldText != NULL ? undo->oldText : "")); 87 88 restoredTextLength = undo->oldText != NULL ? strlen(undo->oldText) : 0; 89 int diff = restoredTextLength; 90 if(diff == 0) { 91 diff = undo->startPos - undo->endPos; 92 } 93 if (!window->buffer->primary.selected || GetPrefUndoModifiesSelection()) { 94 size_t newPos = undo->startPos + restoredTextLength; 95 if(!isBatch) { 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, newPos); 99 } else { 100 cursors[cursorIndex] = newPos; 101 for(int i=cursorIndex-1;i>=0;i--) { 102 cursors[i] += diff; 103 } 104 } 105 } 106 107 if (GetPrefUndoModifiesSelection() && !isBatch) { 108 if (restoredTextLength > 0) { 109 BufSelect(window->buffer, undo->startPos, undo->startPos + 110 restoredTextLength); 111 } 112 else { 113 BufUnselect(window->buffer); 114 } 115 } 116 MakeSelectionVisible(window, window->lastFocus); 117 118 /* restore the file's unmodified status if the file was unmodified 119 when the change being undone was originally made. Also, remove 120 the backup file, since the text in the buffer is now identical to 121 the original file */ 122 if (undo->restoresToSaved) { 123 SetWindowModified(window, False); 124 RemoveBackupFile(window); 125 } 126 127 /* free the undo record and remove it from the chain */ 128 removeUndoItem(window); 129 } 130 131 void Undo(WindowInfo *window) { 132 int numOp = window->undo->numOp; 133 int undoCount = 1; 134 int isBatch = 0; 135 if(numOp > 0) { 136 undoCount = numOp; 137 isBatch = 1; 138 } 139 140 size_t *cursors = NULL; 141 int cursorIndex = 0; 142 TextChangeCursors(window->lastFocus, 0, 0); 143 if(!isBatch) { 144 TextClearMultiCursors(window->lastFocus); 145 } else { 146 cursors = NEditCalloc(sizeof(size_t), numOp); 147 } 148 149 window->undo_op_batch_size = numOp; 150 for(int i=0;i<undoCount;i++) { 151 doUndo(window, isBatch, cursors, cursorIndex++); 152 } 153 154 if(cursors) { 155 TextSetCursors(window->lastFocus, cursors, numOp); 156 NEditFree(cursors); 157 } 158 } 159 160 static void doRedo(WindowInfo *window, int isBatch, size_t *cursors, int cursorIndex) 161 { 162 UndoInfo *redo = window->redo; 163 int restoredTextLength; 164 165 // not really necessary, but in case of redo-bugs, this prevents a crash 166 if (window->redo == NULL) { 167 return; 168 } 169 170 // BufReplace will eventually call SaveUndoInformation. To indicate 171 // to SaveUndoInformation that this is the context of a redo operation, 172 // we set the inUndo indicator in the redo record 173 redo->inUndo = True; 174 175 // use the saved redo information to reverse changes 176 BufReplace(window->buffer, redo->startPos, redo->endPos, 177 (redo->oldText != NULL ? redo->oldText : "")); 178 179 restoredTextLength = redo->oldText != NULL ? strlen(redo->oldText) : 0; 180 if (!window->buffer->primary.selected || GetPrefUndoModifiesSelection()) { 181 // position the cursor in the focus pane after the changed text 182 // to show the user where the undo was done 183 int newpos = redo->startPos + restoredTextLength; 184 if(!isBatch) { 185 TextSetCursorPos(window->lastFocus, newpos); 186 } else { 187 //TextSetLastCursorPos(window->lastFocus, redo->startPos + restoredTextLength); 188 cursors[cursorIndex] = redo->startPos + restoredTextLength; 189 } 190 } 191 if (!isBatch && GetPrefUndoModifiesSelection()) { 192 if (restoredTextLength > 0) { 193 BufSelect(window->buffer, redo->startPos, redo->startPos + 194 restoredTextLength); 195 } 196 else { 197 BufUnselect(window->buffer); 198 } 199 } 200 MakeSelectionVisible(window, window->lastFocus); 201 202 // restore the file's unmodified status if the file was unmodified 203 // when the change being redone was originally made. Also, remove 204 // the backup file, since the text in the buffer is now identical to 205 // the original file 206 if (redo->restoresToSaved) { 207 SetWindowModified(window, False); 208 RemoveBackupFile(window); 209 } 210 211 // remove the redo record from the chain and free it 212 removeRedoItem(window); 213 } 214 215 void Redo(WindowInfo *window) 216 { 217 UndoInfo *redo = window->redo; 218 // return if nothing to redo 219 if (window->redo == NULL) 220 return; 221 222 int numOp = redo->numOp; 223 int redoCount = 1; 224 int isBatch = 0; 225 size_t *cursors = NULL; 226 int cursorIndex = 0; 227 if(numOp > 0) { 228 redoCount = numOp; 229 isBatch = 1; 230 cursors = NEditCalloc(sizeof(size_t), numOp); 231 } 232 233 TextChangeCursors(window->lastFocus, 0, 0); 234 window->undo_op_batch_size = numOp; 235 for(int i=0;i<redoCount;i++) { 236 doRedo(window, isBatch, cursors, cursorIndex++); 237 } 238 239 if(cursors) { 240 TextSetCursors(window->lastFocus, cursors, numOp); 241 NEditFree(cursors); 242 } 243 } 244 245 246 /* 247 ** SaveUndoInformation stores away the changes made to the text buffer. As a 248 ** side effect, it also increments the autoSave operation and character counts 249 ** since it needs to do the classification anyhow. 250 ** 251 ** Note: This routine must be kept efficient. It is called for every 252 ** character typed. 253 */ 254 void SaveUndoInformation(WindowInfo *window, int pos, int nInserted, 255 int nDeleted, const char *deletedText) 256 { 257 int newType, oldType; 258 UndoInfo *u, *undo = window->undo; 259 int isUndo = (undo != NULL && undo->inUndo); 260 int isRedo = (window->redo != NULL && window->redo->inUndo); 261 int numOp = window->undo_op_batch_size; 262 263 /* redo operations become invalid once the user begins typing or does 264 other editing. If this is not a redo or undo operation and a redo 265 list still exists, clear it and dim the redo menu item */ 266 if (!(isUndo || isRedo) && window->redo != NULL) 267 ClearRedoList(window); 268 269 /* figure out what kind of editing operation this is, and recall 270 what the last one was */ 271 newType = determineUndoType(nInserted, nDeleted); 272 if (newType == UNDO_NOOP) 273 return; 274 oldType = (undo == NULL || isUndo) ? UNDO_NOOP : undo->type; 275 276 /* 277 ** Check for continuations of single character operations. These are 278 ** accumulated so a whole insertion or deletion can be undone, rather 279 ** than just the last character that the user typed. If the window 280 ** is currently in an unmodified state, don't accumulate operations 281 ** across the save, so the user can undo back to the unmodified state. 282 ** 283 ** In multi cursor mode, this doesn't work. Multi-cursor modifications 284 ** are indicated by the undo-batch 285 */ 286 if (window->fileChanged && !window->undo_batch_begin) { 287 288 /* normal sequential character insertion */ 289 if ( ((oldType == ONE_CHAR_INSERT || oldType == ONE_CHAR_REPLACE) 290 && newType == ONE_CHAR_INSERT) && (pos == undo->endPos)) { 291 undo->endPos++; 292 window->autoSaveCharCount++; 293 return; 294 } 295 296 /* overstrike mode replacement */ 297 if ((oldType == ONE_CHAR_REPLACE && newType == ONE_CHAR_REPLACE) && 298 (pos == undo->endPos)) { 299 appendDeletedText(window, deletedText, nDeleted, FORWARD); 300 undo->endPos++; 301 window->autoSaveCharCount++; 302 return; 303 } 304 305 /* forward delete */ 306 if ((oldType==ONE_CHAR_DELETE && newType==ONE_CHAR_DELETE) && 307 (pos==undo->startPos)) { 308 appendDeletedText(window, deletedText, nDeleted, FORWARD); 309 return; 310 } 311 312 /* reverse delete */ 313 if ((oldType==ONE_CHAR_DELETE && newType==ONE_CHAR_DELETE) && 314 (pos == undo->startPos-1)) { 315 appendDeletedText(window, deletedText, nDeleted, REVERSE); 316 undo->startPos--; 317 undo->endPos--; 318 return; 319 } 320 } 321 322 /* 323 ** The user has started a new operation, create a new undo record 324 ** and save the new undo data. 325 */ 326 undo = (UndoInfo *)NEditMalloc(sizeof(UndoInfo)); 327 undo->oldLen = 0; 328 undo->oldText = NULL; 329 undo->type = newType; 330 undo->inUndo = False; 331 undo->numOp = numOp; 332 undo->restoresToSaved = False; 333 undo->startPos = pos; 334 undo->endPos = pos + nInserted; 335 336 /* if text was deleted, save it */ 337 if (nDeleted > 0) { 338 undo->oldLen = nDeleted + 1; /* +1 is for null at end */ 339 undo->oldText = (char*)NEditMalloc(nDeleted + 1); 340 strcpy(undo->oldText, deletedText); 341 } 342 343 /* increment the operation count for the autosave feature */ 344 window->autoSaveOpCount++; 345 346 /* if the window is currently unmodified, remove the previous 347 restoresToSaved marker, and set it on this record */ 348 if (!window->fileChanged) { 349 undo->restoresToSaved = True; 350 for (u=window->undo; u!=NULL; u=u->next) 351 u->restoresToSaved = False; 352 for (u=window->redo; u!=NULL; u=u->next) 353 u->restoresToSaved = False; 354 } 355 356 /* Add the new record to the undo list unless SaveUndoInfo is 357 saving information generated by an Undo operation itself, in 358 which case, add the new record to the redo list. */ 359 if (isUndo) 360 addRedoItem(window, undo); 361 else 362 addUndoItem(window, undo); 363 } 364 365 /* 366 ** ClearUndoList, ClearRedoList 367 ** 368 ** Functions for clearing all of the information off of the undo or redo 369 ** lists and adjusting the edit menu accordingly 370 */ 371 void ClearUndoList(WindowInfo *window) 372 { 373 while (window->undo != NULL) 374 removeUndoItem(window); 375 } 376 void ClearRedoList(WindowInfo *window) 377 { 378 while (window->redo != NULL) 379 removeRedoItem(window); 380 } 381 382 /* 383 ** Add an undo record (already allocated by the caller) to the window's undo 384 ** list if the item pushes the undo operation or character counts past the 385 ** limits, trim the undo list to an acceptable length. 386 */ 387 static void addUndoItem(WindowInfo *window, UndoInfo *undo) 388 { 389 390 /* Make the undo menu item sensitive now that there's something to undo */ 391 if (window->undo == NULL) { 392 SetSensitive(window, window->undoItem, True); 393 SetBGMenuUndoSensitivity(window, True); 394 } 395 396 /* Add the item to the beginning of the list */ 397 undo->next = window->undo; 398 window->undo = undo; 399 400 /* Increment the operation and memory counts */ 401 window->undoOpCount++; 402 window->undoMemUsed += undo->oldLen; 403 404 /* Trim the list if it exceeds any of the limits */ 405 if (window->undoOpCount > GetPrefUndoOpLimit()) 406 trimUndoList(window, GetPrefUndoOpTrimTo()); 407 if (window->undoMemUsed > GetPrefUndoWorryLimit()) 408 trimUndoList(window, GetPrefUndoWorryTrimTo()); 409 if (window->undoMemUsed > GetPrefUndoPurgeLimit()) 410 trimUndoList(window, GetPrefUndoPurgeTrimTo()); 411 } 412 413 /* 414 ** Add an item (already allocated by the caller) to the window's redo list. 415 */ 416 static void addRedoItem(WindowInfo *window, UndoInfo *redo) 417 { 418 /* Make the redo menu item sensitive now that there's something to redo */ 419 if (window->redo == NULL) { 420 SetSensitive(window, window->redoItem, True); 421 SetBGMenuRedoSensitivity(window, True); 422 } 423 424 /* Add the item to the beginning of the list */ 425 redo->next = window->redo; 426 window->redo = redo; 427 } 428 429 /* 430 ** Pop (remove and free) the current (front) undo record from the undo list 431 */ 432 static void removeUndoItem(WindowInfo *window) 433 { 434 UndoInfo *undo = window->undo; 435 436 if (undo == NULL) 437 return; 438 439 /* Decrement the operation and memory counts */ 440 window->undoOpCount--; 441 window->undoMemUsed -= undo->oldLen; 442 443 /* Remove and free the item */ 444 window->undo = undo->next; 445 freeUndoRecord(undo); 446 447 /* if there are no more undo records left, dim the Undo menu item */ 448 if (window->undo == NULL) { 449 SetSensitive(window, window->undoItem, False); 450 SetBGMenuUndoSensitivity(window, False); 451 } 452 } 453 454 /* 455 ** Pop (remove and free) the current (front) redo record from the redo list 456 */ 457 static void removeRedoItem(WindowInfo *window) 458 { 459 UndoInfo *redo = window->redo; 460 461 /* Remove and free the item */ 462 window->redo = redo->next; 463 freeUndoRecord(redo); 464 465 /* if there are no more redo records left, dim the Redo menu item */ 466 if (window->redo == NULL) { 467 SetSensitive(window, window->redoItem, False); 468 SetBGMenuRedoSensitivity(window, False); 469 } 470 } 471 472 /* 473 ** Add deleted text to the beginning or end 474 ** of the text saved for undoing the last operation. This routine is intended 475 ** for continuing of a string of one character deletes or replaces, but will 476 ** work with more than one character. 477 */ 478 static void appendDeletedText(WindowInfo *window, const char *deletedText, 479 int deletedLen, int direction) 480 { 481 UndoInfo *undo = window->undo; 482 char *comboText; 483 484 /* re-allocate, adding space for the new character(s) */ 485 comboText = (char*)NEditMalloc(undo->oldLen + deletedLen); 486 487 /* copy the new character and the already deleted text to the new memory */ 488 if (direction == FORWARD) { 489 strcpy(comboText, undo->oldText); 490 strcat(comboText, deletedText); 491 } else { 492 strcpy(comboText, deletedText); 493 strcat(comboText, undo->oldText); 494 } 495 496 /* keep track of the additional memory now used by the undo list */ 497 window->undoMemUsed++; 498 499 /* free the old saved text and attach the new */ 500 NEditFree(undo->oldText); 501 undo->oldText = comboText; 502 undo->oldLen += deletedLen; 503 } 504 505 /* 506 ** Trim records off of the END of the undo list to reduce it to length 507 ** maxLength 508 */ 509 static void trimUndoList(WindowInfo *window, int maxLength) 510 { 511 int i; 512 UndoInfo *u, *lastRec; 513 514 if (window->undo == NULL) 515 return; 516 517 /* Find last item on the list to leave intact */ 518 for (i=1, u=window->undo; i<maxLength && u!=NULL; i++, u=u->next); 519 if (u == NULL) 520 return; 521 522 /* Trim off all subsequent entries */ 523 lastRec = u; 524 while (lastRec->next != NULL) { 525 u = lastRec->next; 526 lastRec->next = u->next; 527 window->undoOpCount--; 528 window->undoMemUsed -= u->oldLen; 529 freeUndoRecord(u); 530 } 531 } 532 533 static int determineUndoType(int nInserted, int nDeleted) 534 { 535 int textDeleted, textInserted; 536 537 textDeleted = (nDeleted > 0); 538 textInserted = (nInserted > 0); 539 540 if (textInserted && !textDeleted) { 541 /* Insert */ 542 if (nInserted == 1) 543 return ONE_CHAR_INSERT; 544 else 545 return BLOCK_INSERT; 546 } else if (textInserted && textDeleted) { 547 /* Replace */ 548 if (nInserted == 1) 549 return ONE_CHAR_REPLACE; 550 else 551 return BLOCK_REPLACE; 552 } else if (!textInserted && textDeleted) { 553 /* Delete */ 554 if (nDeleted == 1) 555 return ONE_CHAR_DELETE; 556 else 557 return BLOCK_DELETE; 558 } else { 559 /* Nothing deleted or inserted */ 560 return UNDO_NOOP; 561 } 562 } 563 564 static void freeUndoRecord(UndoInfo *undo) 565 { 566 if (undo == NULL) 567 return; 568 569 NEditFree(undo->oldText); 570 NEditFree(undo); 571 } 572