jcs's openbsd hax
openbsd
at jcs 600 lines 12 kB view raw
1/* $OpenBSD: undo.c,v 1.59 2023/03/08 04:43:11 guenther Exp $ */ 2/* 3 * This file is in the public domain 4 */ 5 6#include <sys/queue.h> 7#include <signal.h> 8#include <stdio.h> 9#include <stdlib.h> 10#include <string.h> 11 12#include "def.h" 13#include "kbd.h" 14 15#define MAX_FREE_RECORDS 32 16 17/* 18 * Local variables 19 */ 20static struct undoq undo_free; 21static int undo_free_num; 22static int boundary_flag = TRUE; 23static int undo_enable_flag = TRUE; 24 25/* 26 * Local functions 27 */ 28static int find_dot(struct line *, int); 29static int find_lo(int, struct line **, int *, int *); 30static struct undo_rec *new_undo_record(void); 31static int drop_oldest_undo_record(void); 32 33/* 34 * find_dot, find_lo() 35 * 36 * Find an absolute dot in the buffer from a line/offset pair, and vice-versa. 37 * 38 * Since lines can be deleted while they are referenced by undo record, we 39 * need to have an absolute dot to have something reliable. 40 */ 41static int 42find_dot(struct line *lp, int off) 43{ 44 int count = 0; 45 struct line *p; 46 47 for (p = curbp->b_headp; p != lp; p = lforw(p)) { 48 if (count != 0) { 49 if (p == curbp->b_headp) { 50 dobeep(); 51 ewprintf("Error: Undo stuff called with a" 52 "nonexistent line"); 53 return (FALSE); 54 } 55 } 56 count += llength(p) + 1; 57 } 58 count += off; 59 60 return (count); 61} 62 63static int 64find_lo(int pos, struct line **olp, int *offset, int *lnum) 65{ 66 struct line *p; 67 int lineno; 68 69 p = curbp->b_headp; 70 lineno = 0; 71 while (pos > llength(p)) { 72 pos -= llength(p) + 1; 73 if ((p = lforw(p)) == curbp->b_headp) { 74 *olp = NULL; 75 *offset = 0; 76 return (FALSE); 77 } 78 lineno++; 79 } 80 *olp = p; 81 *offset = pos; 82 *lnum = lineno; 83 84 return (TRUE); 85} 86 87static struct undo_rec * 88new_undo_record(void) 89{ 90 struct undo_rec *rec; 91 92 rec = TAILQ_FIRST(&undo_free); 93 if (rec != NULL) { 94 /* Remove it from the free-list */ 95 TAILQ_REMOVE(&undo_free, rec, next); 96 undo_free_num--; 97 } else { 98 if ((rec = malloc(sizeof(*rec))) == NULL) 99 panic("Out of memory in undo code (record)"); 100 } 101 memset(rec, 0, sizeof(struct undo_rec)); 102 103 return (rec); 104} 105 106void 107free_undo_record(struct undo_rec *rec) 108{ 109 static int initialised = 0; 110 111 /* 112 * On the first run, do initialisation of the free list. 113 */ 114 if (initialised == 0) { 115 TAILQ_INIT(&undo_free); 116 initialised = 1; 117 } 118 free(rec->content); 119 rec->content = NULL; 120 if (undo_free_num >= MAX_FREE_RECORDS) { 121 free(rec); 122 return; 123 } 124 undo_free_num++; 125 126 TAILQ_INSERT_HEAD(&undo_free, rec, next); 127} 128 129/* 130 * Drop the oldest undo record in our list. Return 1 if we could remove it, 131 * 0 if the undo list was empty. 132 */ 133static int 134drop_oldest_undo_record(void) 135{ 136 struct undo_rec *rec; 137 138 rec = TAILQ_LAST(&curbp->b_undo, undoq); 139 if (rec != NULL) { 140 undo_free_num--; 141 TAILQ_REMOVE(&curbp->b_undo, rec, next); 142 free_undo_record(rec); 143 return (1); 144 } 145 return (0); 146} 147 148static int 149lastrectype(void) 150{ 151 struct undo_rec *rec; 152 153 if ((rec = TAILQ_FIRST(&curbp->b_undo)) != NULL) 154 return (rec->type); 155 return (0); 156} 157 158/* 159 * Returns TRUE if undo is enabled, FALSE otherwise. 160 */ 161int 162undo_enabled(void) 163{ 164 return (undo_enable_flag); 165} 166 167/* 168 * undo_enable: toggle undo_enable. 169 * Returns the previous value of the flag. 170 */ 171int 172undo_enable(int f, int n) 173{ 174 int pon = undo_enable_flag; 175 176 if (f & (FFARG | FFRAND)) 177 undo_enable_flag = n > 0; 178 else 179 undo_enable_flag = !undo_enable_flag; 180 181 if (!(f & FFRAND)) 182 ewprintf("Undo %sabled", undo_enable_flag ? "en" : "dis"); 183 184 return (pon); 185} 186 187/* 188 * If undo is enabled, then: 189 * Toggle undo boundary recording. 190 * If called with an argument, (n > 0) => enable. Otherwise disable. 191 * In either case, add an undo boundary 192 * If undo is disabled, this function has no effect. 193 */ 194int 195undo_boundary_enable(int f, int n) 196{ 197 int bon = boundary_flag; 198 199 if (!undo_enable_flag) 200 return (FALSE); 201 202 undo_add_boundary(FFRAND, 1); 203 204 if (f & (FFARG | FFRAND)) 205 boundary_flag = n > 0; 206 else 207 boundary_flag = !boundary_flag; 208 209 if (!(f & FFRAND)) 210 ewprintf("Undo boundaries %sabled", 211 boundary_flag ? "en" : "dis"); 212 213 return (bon); 214} 215 216/* 217 * Record an undo boundary, unless boundary_flag == FALSE. 218 * Does nothing if previous undo entry is already a boundary or 'modified' flag. 219 */ 220int 221undo_add_boundary(int f, int n) 222{ 223 struct undo_rec *rec; 224 int last; 225 226 if (boundary_flag == FALSE) 227 return (FALSE); 228 229 last = lastrectype(); 230 if (last == BOUNDARY || last == MODIFIED) 231 return (TRUE); 232 233 rec = new_undo_record(); 234 rec->type = BOUNDARY; 235 236 TAILQ_INSERT_HEAD(&curbp->b_undo, rec, next); 237 238 return (TRUE); 239} 240 241/* 242 * Record an undo "modified" boundary 243 */ 244void 245undo_add_modified(void) 246{ 247 struct undo_rec *rec, *trec; 248 249 TAILQ_FOREACH_SAFE(rec, &curbp->b_undo, next, trec) 250 if (rec->type == MODIFIED) { 251 TAILQ_REMOVE(&curbp->b_undo, rec, next); 252 free_undo_record(rec); 253 } 254 255 rec = new_undo_record(); 256 rec->type = MODIFIED; 257 258 TAILQ_INSERT_HEAD(&curbp->b_undo, rec, next); 259 260 return; 261} 262 263int 264undo_add_insert(struct line *lp, int offset, int size) 265{ 266 struct region reg; 267 struct undo_rec *rec; 268 int pos; 269 270 if (!undo_enable_flag) 271 return (TRUE); 272 273 memset(&reg, 0, sizeof(reg)); 274 reg.r_linep = lp; 275 reg.r_offset = offset; 276 reg.r_size = size; 277 278 pos = find_dot(lp, offset); 279 280 /* 281 * We try to reuse the last undo record to `compress' things. 282 */ 283 rec = TAILQ_FIRST(&curbp->b_undo); 284 if (rec != NULL && rec->type == INSERT) { 285 if (rec->pos + rec->region.r_size == pos) { 286 rec->region.r_size += reg.r_size; 287 return (TRUE); 288 } 289 } 290 291 /* 292 * We couldn't reuse the last undo record, so prepare a new one. 293 */ 294 rec = new_undo_record(); 295 rec->pos = pos; 296 rec->type = INSERT; 297 memmove(&rec->region, &reg, sizeof(struct region)); 298 rec->content = NULL; 299 300 undo_add_boundary(FFRAND, 1); 301 302 TAILQ_INSERT_HEAD(&curbp->b_undo, rec, next); 303 304 return (TRUE); 305} 306 307/* 308 * This of course must be done _before_ the actual deletion is done. 309 */ 310int 311undo_add_delete(struct line *lp, int offset, int size, int isreg) 312{ 313 struct region reg; 314 struct undo_rec *rec; 315 int pos; 316 317 if (!undo_enable_flag) 318 return (TRUE); 319 320 memset(&reg, 0, sizeof(reg)); 321 reg.r_linep = lp; 322 reg.r_offset = offset; 323 reg.r_size = size; 324 325 pos = find_dot(lp, offset); 326 327 if (offset == llength(lp)) /* if it's a newline... */ 328 undo_add_boundary(FFRAND, 1); 329 else if ((rec = TAILQ_FIRST(&curbp->b_undo)) != NULL) { 330 /* 331 * Separate this command from the previous one if we're not 332 * just before the previous record... 333 */ 334 if (!isreg && rec->type == DELETE) { 335 if (rec->pos - rec->region.r_size != pos) 336 undo_add_boundary(FFRAND, 1); 337 } 338 } 339 rec = new_undo_record(); 340 rec->pos = pos; 341 if (isreg) 342 rec->type = DELREG; 343 else 344 rec->type = DELETE; 345 memmove(&rec->region, &reg, sizeof(struct region)); 346 do { 347 rec->content = malloc(reg.r_size + 1); 348 } while ((rec->content == NULL) && drop_oldest_undo_record()); 349 350 if (rec->content == NULL) 351 panic("Out of memory"); 352 353 region_get_data(&reg, rec->content, reg.r_size); 354 355 if (isreg || lastrectype() != DELETE) 356 undo_add_boundary(FFRAND, 1); 357 358 TAILQ_INSERT_HEAD(&curbp->b_undo, rec, next); 359 360 return (TRUE); 361} 362 363/* 364 * This of course must be called before the change takes place. 365 */ 366int 367undo_add_change(struct line *lp, int offset, int size) 368{ 369 if (!undo_enable_flag) 370 return (TRUE); 371 undo_add_boundary(FFRAND, 1); 372 boundary_flag = FALSE; 373 undo_add_delete(lp, offset, size, 0); 374 undo_add_insert(lp, offset, size); 375 boundary_flag = TRUE; 376 undo_add_boundary(FFRAND, 1); 377 378 return (TRUE); 379} 380 381/* 382 * Show the undo records for the current buffer in a new buffer. 383 */ 384int 385undo_dump(int f, int n) 386{ 387 struct undo_rec *rec; 388 struct buffer *bp; 389 struct mgwin *wp; 390 char buf[4096], tmp[1024]; 391 int num; 392 393 /* 394 * Prepare the buffer for insertion. 395 */ 396 if ((bp = bfind("*undo*", TRUE)) == NULL) 397 return (FALSE); 398 bp->b_flag |= BFREADONLY; 399 bclear(bp); 400 if ((wp = popbuf(bp, WNONE)) == NULL) 401 return (FALSE); 402 403 for (wp = wheadp; wp != NULL; wp = wp->w_wndp) { 404 if (wp->w_bufp == bp) { 405 wp->w_dotp = bp->b_headp; 406 wp->w_doto = 0; 407 } 408 } 409 410 num = 0; 411 TAILQ_FOREACH(rec, &curbp->b_undo, next) { 412 num++; 413 snprintf(buf, sizeof(buf), 414 "%d:\t %s at %d ", num, 415 (rec->type == DELETE) ? "DELETE": 416 (rec->type == DELREG) ? "DELREGION": 417 (rec->type == INSERT) ? "INSERT": 418 (rec->type == BOUNDARY) ? "----" : 419 (rec->type == MODIFIED) ? "MODIFIED": "UNKNOWN", 420 rec->pos); 421 422 if (rec->content) { 423 (void)strlcat(buf, "\"", sizeof(buf)); 424 snprintf(tmp, sizeof(tmp), "%.*s", rec->region.r_size, 425 rec->content); 426 (void)strlcat(buf, tmp, sizeof(buf)); 427 (void)strlcat(buf, "\"", sizeof(buf)); 428 } 429 snprintf(tmp, sizeof(tmp), " [%d]", rec->region.r_size); 430 if (strlcat(buf, tmp, sizeof(buf)) >= sizeof(buf)) { 431 dobeep(); 432 ewprintf("Undo record too large. Aborted."); 433 return (FALSE); 434 } 435 addlinef(bp, "%s", buf); 436 } 437 for (wp = wheadp; wp != NULL; wp = wp->w_wndp) { 438 if (wp->w_bufp == bp) { 439 wp->w_dotline = num+1; 440 wp->w_rflag |= WFFULL; 441 } 442 } 443 return (TRUE); 444} 445 446/* 447 * After the user did action1, then action2, then action3: 448 * 449 * [action3] <--- Undoptr 450 * [action2] 451 * [action1] 452 * ------ 453 * [undo] 454 * 455 * After undo: 456 * 457 * [undo of action3] 458 * [action2] <--- Undoptr 459 * [action1] 460 * ------ 461 * [undo] 462 * 463 * After another undo: 464 * 465 * 466 * [undo of action2] 467 * [undo of action3] 468 * [action1] <--- Undoptr 469 * ------ 470 * [undo] 471 * 472 * Note that the "undo of actionX" have no special meaning. Only when 473 * we undo a deletion, the insertion will be recorded just as if it 474 * was typed on the keyboard. Resulting in the inverse operation being 475 * saved in the list. 476 * 477 * If undoptr reaches the bottom of the list, or if we moved between 478 * two undo actions, we make it point back at the topmost record. This is 479 * how we handle redoing. 480 */ 481int 482undo(int f, int n) 483{ 484 struct undo_rec *ptr, *nptr; 485 int done, rval; 486 struct line *lp; 487 int offset, save; 488 static int nulled = FALSE; 489 int lineno; 490 491 if (n < 0) 492 return (FALSE); 493 494 ptr = curbp->b_undoptr; 495 496 /* first invocation, make ptr point back to the top of the list */ 497 if ((ptr == NULL && nulled == TRUE) || rptcount == 0) { 498 ptr = TAILQ_FIRST(&curbp->b_undo); 499 nulled = TRUE; 500 } 501 502 rval = TRUE; 503 while (n--) { 504 /* if we have a spurious boundary, free it and move on.... */ 505 while (ptr && ptr->type == BOUNDARY) { 506 nptr = TAILQ_NEXT(ptr, next); 507 TAILQ_REMOVE(&curbp->b_undo, ptr, next); 508 free_undo_record(ptr); 509 ptr = nptr; 510 } 511 /* 512 * Ptr is NULL, but on the next run, it will point to the 513 * top again, redoing all stuff done in the buffer since 514 * its creation. 515 */ 516 if (ptr == NULL) { 517 dobeep(); 518 ewprintf("No further undo information"); 519 rval = FALSE; 520 nulled = TRUE; 521 break; 522 } 523 nulled = FALSE; 524 525 /* 526 * Loop while we don't get a boundary specifying we've 527 * finished the current action... 528 */ 529 530 undo_add_boundary(FFRAND, 1); 531 532 save = boundary_flag; 533 boundary_flag = FALSE; 534 535 done = 0; 536 do { 537 /* 538 * Move to where this has to apply 539 * 540 * Boundaries (and the modified flag) are put as 541 * position 0 (to save lookup time in find_dot) 542 * so we must not move there... 543 */ 544 if (ptr->type != BOUNDARY && ptr->type != MODIFIED) { 545 if (find_lo(ptr->pos, &lp, 546 &offset, &lineno) == FALSE) { 547 dobeep(); 548 ewprintf("Internal error in Undo!"); 549 rval = FALSE; 550 break; 551 } 552 curwp->w_dotp = lp; 553 curwp->w_doto = offset; 554 curwp->w_markline = curwp->w_dotline; 555 curwp->w_dotline = lineno; 556 } 557 558 /* 559 * Do operation^-1 560 */ 561 switch (ptr->type) { 562 case INSERT: 563 ldelete(ptr->region.r_size, KNONE); 564 break; 565 case DELETE: 566 lp = curwp->w_dotp; 567 offset = curwp->w_doto; 568 region_put_data(ptr->content, 569 ptr->region.r_size); 570 curwp->w_dotp = lp; 571 curwp->w_doto = offset; 572 break; 573 case DELREG: 574 region_put_data(ptr->content, 575 ptr->region.r_size); 576 break; 577 case BOUNDARY: 578 done = 1; 579 break; 580 case MODIFIED: 581 curbp->b_flag &= ~BFCHG; 582 break; 583 default: 584 break; 585 } 586 587 /* And move to next record */ 588 ptr = TAILQ_NEXT(ptr, next); 589 } while (ptr != NULL && !done); 590 591 boundary_flag = save; 592 undo_add_boundary(FFRAND, 1); 593 594 ewprintf("Undo!"); 595 } 596 597 curbp->b_undoptr = ptr; 598 599 return (rval); 600}