1 /*- 2 * Copyright (c) 1993, 1994 3 * The Regents of the University of California. All rights reserved. 4 * Copyright (c) 1993, 1994, 1995, 1996 5 * Keith Bostic. All rights reserved. 6 * 7 * See the LICENSE file for redistribution information. 8 */ 9 10 #include "config.h" 11 12 #ifndef lint 13 static const char sccsid[] = "@(#)vs_smap.c 10.25 (Berkeley) 7/12/96"; 14 #endif /* not lint */ 15 16 #include <sys/types.h> 17 #include <sys/queue.h> 18 #include <sys/time.h> 19 20 #include <bitstring.h> 21 #include <limits.h> 22 #include <stdio.h> 23 #include <stdlib.h> 24 #include <string.h> 25 26 #include "../common/common.h" 27 #include "vi.h" 28 29 static int vs_deleteln __P((SCR *, int)); 30 static int vs_insertln __P((SCR *, int)); 31 static int vs_sm_delete __P((SCR *, recno_t)); 32 static int vs_sm_down __P((SCR *, MARK *, recno_t, scroll_t, SMAP *)); 33 static int vs_sm_erase __P((SCR *)); 34 static int vs_sm_insert __P((SCR *, recno_t)); 35 static int vs_sm_reset __P((SCR *, recno_t)); 36 static int vs_sm_up __P((SCR *, MARK *, recno_t, scroll_t, SMAP *)); 37 38 /* 39 * vs_change -- 40 * Make a change to the screen. 41 * 42 * PUBLIC: int vs_change __P((SCR *, recno_t, lnop_t)); 43 */ 44 int 45 vs_change(sp, lno, op) 46 SCR *sp; 47 recno_t lno; 48 lnop_t op; 49 { 50 VI_PRIVATE *vip; 51 SMAP *p; 52 size_t cnt, oldy, oldx; 53 54 vip = VIP(sp); 55 56 /* 57 * XXX 58 * Very nasty special case. The historic vi code displays a single 59 * space (or a '$' if the list option is set) for the first line in 60 * an "empty" file. If we "insert" a line, that line gets scrolled 61 * down, not repainted, so it's incorrect when we refresh the screen. 62 * The vi text input functions detect it explicitly and don't insert 63 * a new line. 64 * 65 * Check for line #2 before going to the end of the file. 66 */ 67 if ((op == LINE_APPEND && lno == 0 || op == LINE_INSERT && lno == 1) && 68 !db_exist(sp, 2)) { 69 lno = 1; 70 op = LINE_RESET; 71 } 72 73 /* Appending is the same as inserting, if the line is incremented. */ 74 if (op == LINE_APPEND) { 75 ++lno; 76 op = LINE_INSERT; 77 } 78 79 /* Ignore the change if the line is after the map. */ 80 if (lno > TMAP->lno) 81 return (0); 82 83 /* 84 * If the line is before the map, and it's a decrement, decrement 85 * the map. If it's an increment, increment the map. Otherwise, 86 * ignore it. 87 */ 88 if (lno < HMAP->lno) { 89 switch (op) { 90 case LINE_APPEND: 91 abort(); 92 /* NOTREACHED */ 93 case LINE_DELETE: 94 for (p = HMAP, cnt = sp->t_rows; cnt--; ++p) 95 --p->lno; 96 if (sp->lno >= lno) 97 --sp->lno; 98 F_SET(vip, VIP_N_RENUMBER); 99 break; 100 case LINE_INSERT: 101 for (p = HMAP, cnt = sp->t_rows; cnt--; ++p) 102 ++p->lno; 103 if (sp->lno >= lno) 104 ++sp->lno; 105 F_SET(vip, VIP_N_RENUMBER); 106 break; 107 case LINE_RESET: 108 break; 109 } 110 return (0); 111 } 112 113 F_SET(vip, VIP_N_REFRESH); 114 115 /* 116 * Invalidate the line size cache, and invalidate the cursor if it's 117 * on this line, 118 */ 119 VI_SCR_CFLUSH(vip); 120 if (sp->lno == lno) 121 F_SET(vip, VIP_CUR_INVALID); 122 123 /* 124 * If ex modifies the screen after ex output is already on the screen 125 * or if we've switched into ex canonical mode, don't touch it -- we'll 126 * get scrolling wrong, at best. 127 */ 128 if (!F_ISSET(sp, SC_TINPUT_INFO) && 129 (F_ISSET(sp, SC_SCR_EXWROTE) || VIP(sp)->totalcount > 1)) { 130 F_SET(vip, VIP_N_EX_REDRAW); 131 return (0); 132 } 133 134 /* Save and restore the cursor for these routines. */ 135 (void)sp->gp->scr_cursor(sp, &oldy, &oldx); 136 137 switch (op) { 138 case LINE_DELETE: 139 if (vs_sm_delete(sp, lno)) 140 return (1); 141 F_SET(vip, VIP_N_RENUMBER); 142 break; 143 case LINE_INSERT: 144 if (vs_sm_insert(sp, lno)) 145 return (1); 146 F_SET(vip, VIP_N_RENUMBER); 147 break; 148 case LINE_RESET: 149 if (vs_sm_reset(sp, lno)) 150 return (1); 151 break; 152 default: 153 abort(); 154 } 155 156 (void)sp->gp->scr_move(sp, oldy, oldx); 157 return (0); 158 } 159 160 /* 161 * vs_sm_fill -- 162 * Fill in the screen map, placing the specified line at the 163 * right position. There isn't any way to tell if an SMAP 164 * entry has been filled in, so this routine had better be 165 * called with P_FILL set before anything else is done. 166 * 167 * !!! 168 * Unexported interface: if lno is OOBLNO, P_TOP means that the HMAP 169 * slot is already filled in, P_BOTTOM means that the TMAP slot is 170 * already filled in, and we just finish up the job. 171 * 172 * PUBLIC: int vs_sm_fill __P((SCR *, recno_t, pos_t)); 173 */ 174 int 175 vs_sm_fill(sp, lno, pos) 176 SCR *sp; 177 recno_t lno; 178 pos_t pos; 179 { 180 SMAP *p, tmp; 181 size_t cnt; 182 183 /* Flush all cached information from the SMAP. */ 184 for (p = HMAP, cnt = sp->t_rows; cnt--; ++p) 185 SMAP_FLUSH(p); 186 187 /* 188 * If the map is filled, the screen must be redrawn. 189 * 190 * XXX 191 * This is a bug. We should try and figure out if the desired line 192 * is already in the map or close by -- scrolling the screen would 193 * be a lot better than redrawing. 194 */ 195 F_SET(sp, SC_SCR_REDRAW); 196 197 switch (pos) { 198 case P_FILL: 199 tmp.lno = 1; 200 tmp.coff = 0; 201 tmp.soff = 1; 202 203 /* See if less than half a screen from the top. */ 204 if (vs_sm_nlines(sp, 205 &tmp, lno, HALFTEXT(sp)) <= HALFTEXT(sp)) { 206 lno = 1; 207 goto top; 208 } 209 210 /* See if less than half a screen from the bottom. */ 211 if (db_last(sp, &tmp.lno)) 212 return (1); 213 tmp.coff = 0; 214 tmp.soff = vs_screens(sp, tmp.lno, NULL); 215 if (vs_sm_nlines(sp, 216 &tmp, lno, HALFTEXT(sp)) <= HALFTEXT(sp)) { 217 TMAP->lno = tmp.lno; 218 TMAP->coff = tmp.coff; 219 TMAP->soff = tmp.soff; 220 goto bottom; 221 } 222 goto middle; 223 case P_TOP: 224 if (lno != OOBLNO) { 225 top: HMAP->lno = lno; 226 HMAP->coff = 0; 227 HMAP->soff = 1; 228 } 229 /* If we fail, just punt. */ 230 for (p = HMAP, cnt = sp->t_rows; --cnt; ++p) 231 if (vs_sm_next(sp, p, p + 1)) 232 goto err; 233 break; 234 case P_MIDDLE: 235 /* If we fail, guess that the file is too small. */ 236 middle: p = HMAP + sp->t_rows / 2; 237 p->lno = lno; 238 p->coff = 0; 239 p->soff = 1; 240 for (; p > HMAP; --p) 241 if (vs_sm_prev(sp, p, p - 1)) { 242 lno = 1; 243 goto top; 244 } 245 246 /* If we fail, just punt. */ 247 p = HMAP + sp->t_rows / 2; 248 for (; p < TMAP; ++p) 249 if (vs_sm_next(sp, p, p + 1)) 250 goto err; 251 break; 252 case P_BOTTOM: 253 if (lno != OOBLNO) { 254 TMAP->lno = lno; 255 TMAP->coff = 0; 256 TMAP->soff = vs_screens(sp, lno, NULL); 257 } 258 /* If we fail, guess that the file is too small. */ 259 bottom: for (p = TMAP; p > HMAP; --p) 260 if (vs_sm_prev(sp, p, p - 1)) { 261 lno = 1; 262 goto top; 263 } 264 break; 265 default: 266 abort(); 267 } 268 return (0); 269 270 /* 271 * Try and put *something* on the screen. If this fails, we have a 272 * serious hard error. 273 */ 274 err: HMAP->lno = 1; 275 HMAP->coff = 0; 276 HMAP->soff = 1; 277 for (p = HMAP; p < TMAP; ++p) 278 if (vs_sm_next(sp, p, p + 1)) 279 return (1); 280 return (0); 281 } 282 283 /* 284 * For the routines vs_sm_reset, vs_sm_delete and vs_sm_insert: if the 285 * screen contains only a single line (whether because the screen is small 286 * or the line large), it gets fairly exciting. Skip the fun, set a flag 287 * so the screen map is refilled and the screen redrawn, and return. This 288 * is amazingly slow, but it's not clear that anyone will care. 289 */ 290 #define HANDLE_WEIRDNESS(cnt) { \ 291 if (cnt >= sp->t_rows) { \ 292 F_SET(sp, SC_SCR_REFORMAT); \ 293 return (0); \ 294 } \ 295 } 296 297 /* 298 * vs_sm_delete -- 299 * Delete a line out of the SMAP. 300 */ 301 static int 302 vs_sm_delete(sp, lno) 303 SCR *sp; 304 recno_t lno; 305 { 306 SMAP *p, *t; 307 size_t cnt_orig; 308 309 /* 310 * Find the line in the map, and count the number of screen lines 311 * which display any part of the deleted line. 312 */ 313 for (p = HMAP; p->lno != lno; ++p); 314 if (O_ISSET(sp, O_LEFTRIGHT)) 315 cnt_orig = 1; 316 else 317 for (cnt_orig = 1, t = p + 1; 318 t <= TMAP && t->lno == lno; ++cnt_orig, ++t); 319 320 HANDLE_WEIRDNESS(cnt_orig); 321 322 /* Delete that many lines from the screen. */ 323 (void)sp->gp->scr_move(sp, p - HMAP, 0); 324 if (vs_deleteln(sp, cnt_orig)) 325 return (1); 326 327 /* Shift the screen map up. */ 328 memmove(p, p + cnt_orig, (((TMAP - p) - cnt_orig) + 1) * sizeof(SMAP)); 329 330 /* Decrement the line numbers for the rest of the map. */ 331 for (t = TMAP - cnt_orig; p <= t; ++p) 332 --p->lno; 333 334 /* Display the new lines. */ 335 for (p = TMAP - cnt_orig;;) { 336 if (p < TMAP && vs_sm_next(sp, p, p + 1)) 337 return (1); 338 /* vs_sm_next() flushed the cache. */ 339 if (vs_line(sp, ++p, NULL, NULL)) 340 return (1); 341 if (p == TMAP) 342 break; 343 } 344 return (0); 345 } 346 347 /* 348 * vs_sm_insert -- 349 * Insert a line into the SMAP. 350 */ 351 static int 352 vs_sm_insert(sp, lno) 353 SCR *sp; 354 recno_t lno; 355 { 356 SMAP *p, *t; 357 size_t cnt_orig, cnt, coff; 358 359 /* Save the offset. */ 360 coff = HMAP->coff; 361 362 /* 363 * Find the line in the map, find out how many screen lines 364 * needed to display the line. 365 */ 366 for (p = HMAP; p->lno != lno; ++p); 367 368 cnt_orig = vs_screens(sp, lno, NULL); 369 HANDLE_WEIRDNESS(cnt_orig); 370 371 /* 372 * The lines left in the screen override the number of screen 373 * lines in the inserted line. 374 */ 375 cnt = (TMAP - p) + 1; 376 if (cnt_orig > cnt) 377 cnt_orig = cnt; 378 379 /* Push down that many lines. */ 380 (void)sp->gp->scr_move(sp, p - HMAP, 0); 381 if (vs_insertln(sp, cnt_orig)) 382 return (1); 383 384 /* Shift the screen map down. */ 385 memmove(p + cnt_orig, p, (((TMAP - p) - cnt_orig) + 1) * sizeof(SMAP)); 386 387 /* Increment the line numbers for the rest of the map. */ 388 for (t = p + cnt_orig; t <= TMAP; ++t) 389 ++t->lno; 390 391 /* Fill in the SMAP for the new lines, and display. */ 392 for (cnt = 1, t = p; cnt <= cnt_orig; ++t, ++cnt) { 393 t->lno = lno; 394 t->coff = coff; 395 t->soff = cnt; 396 SMAP_FLUSH(t); 397 if (vs_line(sp, t, NULL, NULL)) 398 return (1); 399 } 400 return (0); 401 } 402 403 /* 404 * vs_sm_reset -- 405 * Reset a line in the SMAP. 406 */ 407 static int 408 vs_sm_reset(sp, lno) 409 SCR *sp; 410 recno_t lno; 411 { 412 SMAP *p, *t; 413 size_t cnt_orig, cnt_new, cnt, diff; 414 415 /* 416 * See if the number of on-screen rows taken up by the old display 417 * for the line is the same as the number needed for the new one. 418 * If so, repaint, otherwise do it the hard way. 419 */ 420 for (p = HMAP; p->lno != lno; ++p); 421 if (O_ISSET(sp, O_LEFTRIGHT)) { 422 t = p; 423 cnt_orig = cnt_new = 1; 424 } else { 425 for (cnt_orig = 0, 426 t = p; t <= TMAP && t->lno == lno; ++cnt_orig, ++t); 427 cnt_new = vs_screens(sp, lno, NULL); 428 } 429 430 HANDLE_WEIRDNESS(cnt_orig); 431 432 if (cnt_orig == cnt_new) { 433 do { 434 SMAP_FLUSH(p); 435 if (vs_line(sp, p, NULL, NULL)) 436 return (1); 437 } while (++p < t); 438 return (0); 439 } 440 441 if (cnt_orig < cnt_new) { 442 /* Get the difference. */ 443 diff = cnt_new - cnt_orig; 444 445 /* 446 * The lines left in the screen override the number of screen 447 * lines in the inserted line. 448 */ 449 cnt = (TMAP - p) + 1; 450 if (diff > cnt) 451 diff = cnt; 452 453 /* If there are any following lines, push them down. */ 454 if (cnt > 1) { 455 (void)sp->gp->scr_move(sp, p - HMAP, 0); 456 if (vs_insertln(sp, diff)) 457 return (1); 458 459 /* Shift the screen map down. */ 460 memmove(p + diff, p, 461 (((TMAP - p) - diff) + 1) * sizeof(SMAP)); 462 } 463 464 /* Fill in the SMAP for the replaced line, and display. */ 465 for (cnt = 1, t = p; cnt_new-- && t <= TMAP; ++t, ++cnt) { 466 t->lno = lno; 467 t->soff = cnt; 468 SMAP_FLUSH(t); 469 if (vs_line(sp, t, NULL, NULL)) 470 return (1); 471 } 472 } else { 473 /* Get the difference. */ 474 diff = cnt_orig - cnt_new; 475 476 /* Delete that many lines from the screen. */ 477 (void)sp->gp->scr_move(sp, p - HMAP, 0); 478 if (vs_deleteln(sp, diff)) 479 return (1); 480 481 /* Shift the screen map up. */ 482 memmove(p, p + diff, (((TMAP - p) - diff) + 1) * sizeof(SMAP)); 483 484 /* Fill in the SMAP for the replaced line, and display. */ 485 for (cnt = 1, t = p; cnt_new--; ++t, ++cnt) { 486 t->lno = lno; 487 t->soff = cnt; 488 SMAP_FLUSH(t); 489 if (vs_line(sp, t, NULL, NULL)) 490 return (1); 491 } 492 493 /* Display the new lines at the bottom of the screen. */ 494 for (t = TMAP - diff;;) { 495 if (t < TMAP && vs_sm_next(sp, t, t + 1)) 496 return (1); 497 /* vs_sm_next() flushed the cache. */ 498 if (vs_line(sp, ++t, NULL, NULL)) 499 return (1); 500 if (t == TMAP) 501 break; 502 } 503 } 504 return (0); 505 } 506 507 /* 508 * vs_sm_scroll 509 * Scroll the SMAP up/down count logical lines. Different 510 * semantics based on the vi command, *sigh*. 511 * 512 * PUBLIC: int vs_sm_scroll __P((SCR *, MARK *, recno_t, scroll_t)); 513 */ 514 int 515 vs_sm_scroll(sp, rp, count, scmd) 516 SCR *sp; 517 MARK *rp; 518 recno_t count; 519 scroll_t scmd; 520 { 521 SMAP *smp; 522 523 /* 524 * Invalidate the cursor. The line is probably going to change, 525 * (although for ^E and ^Y it may not). In any case, the scroll 526 * routines move the cursor to draw things. 527 */ 528 F_SET(VIP(sp), VIP_CUR_INVALID); 529 530 /* Find the cursor in the screen. */ 531 if (vs_sm_cursor(sp, &smp)) 532 return (1); 533 534 switch (scmd) { 535 case CNTRL_B: 536 case CNTRL_U: 537 case CNTRL_Y: 538 case Z_CARAT: 539 if (vs_sm_down(sp, rp, count, scmd, smp)) 540 return (1); 541 break; 542 case CNTRL_D: 543 case CNTRL_E: 544 case CNTRL_F: 545 case Z_PLUS: 546 if (vs_sm_up(sp, rp, count, scmd, smp)) 547 return (1); 548 break; 549 default: 550 abort(); 551 } 552 553 /* 554 * !!! 555 * If we're at the start of a line, go for the first non-blank. 556 * This makes it look like the old vi, even though we're moving 557 * around by logical lines, not physical ones. 558 * 559 * XXX 560 * In the presence of a long line, which has more than a screen 561 * width of leading spaces, this code can cause a cursor warp. 562 * Live with it. 563 */ 564 if (scmd != CNTRL_E && scmd != CNTRL_Y && 565 rp->cno == 0 && nonblank(sp, rp->lno, &rp->cno)) 566 return (1); 567 568 return (0); 569 } 570 571 /* 572 * vs_sm_up -- 573 * Scroll the SMAP up count logical lines. 574 */ 575 static int 576 vs_sm_up(sp, rp, count, scmd, smp) 577 SCR *sp; 578 MARK *rp; 579 scroll_t scmd; 580 recno_t count; 581 SMAP *smp; 582 { 583 int cursor_set, echanged, zset; 584 SMAP *ssmp, s1, s2; 585 586 /* 587 * Check to see if movement is possible. 588 * 589 * Get the line after the map. If that line is a new one (and if 590 * O_LEFTRIGHT option is set, this has to be true), and the next 591 * line doesn't exist, and the cursor doesn't move, or the cursor 592 * isn't even on the screen, or the cursor is already at the last 593 * line in the map, it's an error. If that test succeeded because 594 * the cursor wasn't at the end of the map, test to see if the map 595 * is mostly empty. 596 */ 597 if (vs_sm_next(sp, TMAP, &s1)) 598 return (1); 599 if (s1.lno > TMAP->lno && !db_exist(sp, s1.lno)) { 600 if (scmd == CNTRL_E || scmd == Z_PLUS || smp == TMAP) { 601 v_eof(sp, NULL); 602 return (1); 603 } 604 if (vs_sm_next(sp, smp, &s1)) 605 return (1); 606 if (s1.lno > smp->lno && !db_exist(sp, s1.lno)) { 607 v_eof(sp, NULL); 608 return (1); 609 } 610 } 611 612 /* 613 * Small screens: see vs_refresh.c section 6a. 614 * 615 * If it's a small screen, and the movement isn't larger than a 616 * screen, i.e some context will remain, open up the screen and 617 * display by scrolling. In this case, the cursor moves down one 618 * line for each line displayed. Otherwise, erase/compress and 619 * repaint, and move the cursor to the first line in the screen. 620 * Note, the ^F command is always in the latter case, for historical 621 * reasons. 622 */ 623 cursor_set = 0; 624 if (IS_SMALL(sp)) { 625 if (count >= sp->t_maxrows || scmd == CNTRL_F) { 626 s1 = TMAP[0]; 627 if (vs_sm_erase(sp)) 628 return (1); 629 for (; count--; s1 = s2) { 630 if (vs_sm_next(sp, &s1, &s2)) 631 return (1); 632 if (s2.lno != s1.lno && !db_exist(sp, s2.lno)) 633 break; 634 } 635 TMAP[0] = s2; 636 if (vs_sm_fill(sp, OOBLNO, P_BOTTOM)) 637 return (1); 638 return (vs_sm_position(sp, rp, 0, P_TOP)); 639 } 640 cursor_set = scmd == CNTRL_E || vs_sm_cursor(sp, &ssmp); 641 for (; count && 642 sp->t_rows != sp->t_maxrows; --count, ++sp->t_rows) { 643 if (vs_sm_next(sp, TMAP, &s1)) 644 return (1); 645 if (TMAP->lno != s1.lno && !db_exist(sp, s1.lno)) 646 break; 647 *++TMAP = s1; 648 /* vs_sm_next() flushed the cache. */ 649 if (vs_line(sp, TMAP, NULL, NULL)) 650 return (1); 651 652 if (!cursor_set) 653 ++ssmp; 654 } 655 if (!cursor_set) { 656 rp->lno = ssmp->lno; 657 rp->cno = ssmp->c_sboff; 658 } 659 if (count == 0) 660 return (0); 661 } 662 663 for (echanged = zset = 0; count; --count) { 664 /* Decide what would show up on the screen. */ 665 if (vs_sm_next(sp, TMAP, &s1)) 666 return (1); 667 668 /* If the line doesn't exist, we're done. */ 669 if (TMAP->lno != s1.lno && !db_exist(sp, s1.lno)) 670 break; 671 672 /* Scroll the screen cursor up one logical line. */ 673 if (vs_sm_1up(sp)) 674 return (1); 675 switch (scmd) { 676 case CNTRL_E: 677 if (smp > HMAP) 678 --smp; 679 else 680 echanged = 1; 681 break; 682 case Z_PLUS: 683 if (zset) { 684 if (smp > HMAP) 685 --smp; 686 } else { 687 smp = TMAP; 688 zset = 1; 689 } 690 /* FALLTHROUGH */ 691 default: 692 break; 693 } 694 } 695 696 if (cursor_set) 697 return(0); 698 699 switch (scmd) { 700 case CNTRL_E: 701 /* 702 * On a ^E that was forced to change lines, try and keep the 703 * cursor as close as possible to the last position, but also 704 * set it up so that the next "real" movement will return the 705 * cursor to the closest position to the last real movement. 706 */ 707 if (echanged) { 708 rp->lno = smp->lno; 709 rp->cno = vs_colpos(sp, smp->lno, 710 (O_ISSET(sp, O_LEFTRIGHT) ? 711 smp->coff : (smp->soff - 1) * sp->cols) + 712 sp->rcm % sp->cols); 713 } 714 return (0); 715 case CNTRL_F: 716 /* 717 * If there are more lines, the ^F command is positioned at 718 * the first line of the screen. 719 */ 720 if (!count) { 721 smp = HMAP; 722 break; 723 } 724 /* FALLTHROUGH */ 725 case CNTRL_D: 726 /* 727 * The ^D and ^F commands move the cursor towards EOF 728 * if there are more lines to move. Check to be sure 729 * the lines actually exist. (They may not if the 730 * file is smaller than the screen.) 731 */ 732 for (; count; --count, ++smp) 733 if (smp == TMAP || !db_exist(sp, smp[1].lno)) 734 break; 735 break; 736 case Z_PLUS: 737 /* The z+ command moves the cursor to the first new line. */ 738 break; 739 default: 740 abort(); 741 } 742 743 if (!SMAP_CACHE(smp) && vs_line(sp, smp, NULL, NULL)) 744 return (1); 745 rp->lno = smp->lno; 746 rp->cno = smp->c_scoff == 255 ? 0 : smp->c_sboff; 747 return (0); 748 } 749 750 /* 751 * vs_sm_1up -- 752 * Scroll the SMAP up one. 753 * 754 * PUBLIC: int vs_sm_1up __P((SCR *)); 755 */ 756 int 757 vs_sm_1up(sp) 758 SCR *sp; 759 { 760 /* 761 * Delete the top line of the screen. Shift the screen map 762 * up and display a new line at the bottom of the screen. 763 */ 764 (void)sp->gp->scr_move(sp, 0, 0); 765 if (vs_deleteln(sp, 1)) 766 return (1); 767 768 /* One-line screens can fail. */ 769 if (IS_ONELINE(sp)) { 770 if (vs_sm_next(sp, TMAP, TMAP)) 771 return (1); 772 } else { 773 memmove(HMAP, HMAP + 1, (sp->rows - 1) * sizeof(SMAP)); 774 if (vs_sm_next(sp, TMAP - 1, TMAP)) 775 return (1); 776 } 777 /* vs_sm_next() flushed the cache. */ 778 return (vs_line(sp, TMAP, NULL, NULL)); 779 } 780 781 /* 782 * vs_deleteln -- 783 * Delete a line a la curses, make sure to put the information 784 * line and other screens back. 785 */ 786 static int 787 vs_deleteln(sp, cnt) 788 SCR *sp; 789 int cnt; 790 { 791 GS *gp; 792 size_t oldy, oldx; 793 794 gp = sp->gp; 795 if (IS_ONELINE(sp)) 796 (void)gp->scr_clrtoeol(sp); 797 else { 798 (void)gp->scr_cursor(sp, &oldy, &oldx); 799 while (cnt--) { 800 (void)gp->scr_deleteln(sp); 801 (void)gp->scr_move(sp, LASTLINE(sp), 0); 802 (void)gp->scr_insertln(sp); 803 (void)gp->scr_move(sp, oldy, oldx); 804 } 805 } 806 return (0); 807 } 808 809 /* 810 * vs_sm_down -- 811 * Scroll the SMAP down count logical lines. 812 */ 813 static int 814 vs_sm_down(sp, rp, count, scmd, smp) 815 SCR *sp; 816 MARK *rp; 817 recno_t count; 818 SMAP *smp; 819 scroll_t scmd; 820 { 821 SMAP *ssmp, s1, s2; 822 int cursor_set, ychanged, zset; 823 824 /* Check to see if movement is possible. */ 825 if (HMAP->lno == 1 && 826 (O_ISSET(sp, O_LEFTRIGHT) || HMAP->soff == 1) && 827 (scmd == CNTRL_Y || scmd == Z_CARAT || smp == HMAP)) { 828 v_sof(sp, NULL); 829 return (1); 830 } 831 832 /* 833 * Small screens: see vs_refresh.c section 6a. 834 * 835 * If it's a small screen, and the movement isn't larger than a 836 * screen, i.e some context will remain, open up the screen and 837 * display by scrolling. In this case, the cursor moves up one 838 * line for each line displayed. Otherwise, erase/compress and 839 * repaint, and move the cursor to the first line in the screen. 840 * Note, the ^B command is always in the latter case, for historical 841 * reasons. 842 */ 843 cursor_set = scmd == CNTRL_Y; 844 if (IS_SMALL(sp)) { 845 if (count >= sp->t_maxrows || scmd == CNTRL_B) { 846 s1 = HMAP[0]; 847 if (vs_sm_erase(sp)) 848 return (1); 849 for (; count--; s1 = s2) { 850 if (vs_sm_prev(sp, &s1, &s2)) 851 return (1); 852 if (s2.lno == 1 && 853 (O_ISSET(sp, O_LEFTRIGHT) || s2.soff == 1)) 854 break; 855 } 856 HMAP[0] = s2; 857 if (vs_sm_fill(sp, OOBLNO, P_TOP)) 858 return (1); 859 return (vs_sm_position(sp, rp, 0, P_BOTTOM)); 860 } 861 cursor_set = scmd == CNTRL_Y || vs_sm_cursor(sp, &ssmp); 862 for (; count && 863 sp->t_rows != sp->t_maxrows; --count, ++sp->t_rows) { 864 if (HMAP->lno == 1 && 865 (O_ISSET(sp, O_LEFTRIGHT) || HMAP->soff == 1)) 866 break; 867 ++TMAP; 868 if (vs_sm_1down(sp)) 869 return (1); 870 } 871 if (!cursor_set) { 872 rp->lno = ssmp->lno; 873 rp->cno = ssmp->c_sboff; 874 } 875 if (count == 0) 876 return (0); 877 } 878 879 for (ychanged = zset = 0; count; --count) { 880 /* If the line doesn't exist, we're done. */ 881 if (HMAP->lno == 1 && 882 (O_ISSET(sp, O_LEFTRIGHT) || HMAP->soff == 1)) 883 break; 884 885 /* Scroll the screen and cursor down one logical line. */ 886 if (vs_sm_1down(sp)) 887 return (1); 888 switch (scmd) { 889 case CNTRL_Y: 890 if (smp < TMAP) 891 ++smp; 892 else 893 ychanged = 1; 894 break; 895 case Z_CARAT: 896 if (zset) { 897 if (smp < TMAP) 898 ++smp; 899 } else { 900 smp = HMAP; 901 zset = 1; 902 } 903 /* FALLTHROUGH */ 904 default: 905 break; 906 } 907 } 908 909 if (scmd != CNTRL_Y && cursor_set) 910 return(0); 911 912 switch (scmd) { 913 case CNTRL_B: 914 /* 915 * If there are more lines, the ^B command is positioned at 916 * the last line of the screen. However, the line may not 917 * exist. 918 */ 919 if (!count) { 920 for (smp = TMAP; smp > HMAP; --smp) 921 if (db_exist(sp, smp->lno)) 922 break; 923 break; 924 } 925 /* FALLTHROUGH */ 926 case CNTRL_U: 927 /* 928 * The ^B and ^U commands move the cursor towards SOF 929 * if there are more lines to move. 930 */ 931 if (count < smp - HMAP) 932 smp -= count; 933 else 934 smp = HMAP; 935 break; 936 case CNTRL_Y: 937 /* 938 * On a ^Y that was forced to change lines, try and keep the 939 * cursor as close as possible to the last position, but also 940 * set it up so that the next "real" movement will return the 941 * cursor to the closest position to the last real movement. 942 */ 943 if (ychanged) { 944 rp->lno = smp->lno; 945 rp->cno = vs_colpos(sp, smp->lno, 946 (O_ISSET(sp, O_LEFTRIGHT) ? 947 smp->coff : (smp->soff - 1) * sp->cols) + 948 sp->rcm % sp->cols); 949 } 950 return (0); 951 case Z_CARAT: 952 /* The z^ command moves the cursor to the first new line. */ 953 break; 954 default: 955 abort(); 956 } 957 958 if (!SMAP_CACHE(smp) && vs_line(sp, smp, NULL, NULL)) 959 return (1); 960 rp->lno = smp->lno; 961 rp->cno = smp->c_scoff == 255 ? 0 : smp->c_sboff; 962 return (0); 963 } 964 965 /* 966 * vs_sm_erase -- 967 * Erase the small screen area for the scrolling functions. 968 */ 969 static int 970 vs_sm_erase(sp) 971 SCR *sp; 972 { 973 GS *gp; 974 975 gp = sp->gp; 976 (void)gp->scr_move(sp, LASTLINE(sp), 0); 977 (void)gp->scr_clrtoeol(sp); 978 for (; sp->t_rows > sp->t_minrows; --sp->t_rows, --TMAP) { 979 (void)gp->scr_move(sp, TMAP - HMAP, 0); 980 (void)gp->scr_clrtoeol(sp); 981 } 982 return (0); 983 } 984 985 /* 986 * vs_sm_1down -- 987 * Scroll the SMAP down one. 988 * 989 * PUBLIC: int vs_sm_1down __P((SCR *)); 990 */ 991 int 992 vs_sm_1down(sp) 993 SCR *sp; 994 { 995 /* 996 * Insert a line at the top of the screen. Shift the screen map 997 * down and display a new line at the top of the screen. 998 */ 999 (void)sp->gp->scr_move(sp, 0, 0); 1000 if (vs_insertln(sp, 1)) 1001 return (1); 1002 1003 /* One-line screens can fail. */ 1004 if (IS_ONELINE(sp)) { 1005 if (vs_sm_prev(sp, HMAP, HMAP)) 1006 return (1); 1007 } else { 1008 memmove(HMAP + 1, HMAP, (sp->rows - 1) * sizeof(SMAP)); 1009 if (vs_sm_prev(sp, HMAP + 1, HMAP)) 1010 return (1); 1011 } 1012 /* vs_sm_prev() flushed the cache. */ 1013 return (vs_line(sp, HMAP, NULL, NULL)); 1014 } 1015 1016 /* 1017 * vs_insertln -- 1018 * Insert a line a la curses, make sure to put the information 1019 * line and other screens back. 1020 */ 1021 static int 1022 vs_insertln(sp, cnt) 1023 SCR *sp; 1024 int cnt; 1025 { 1026 GS *gp; 1027 size_t oldy, oldx; 1028 1029 gp = sp->gp; 1030 if (IS_ONELINE(sp)) { 1031 (void)gp->scr_move(sp, LASTLINE(sp), 0); 1032 (void)gp->scr_clrtoeol(sp); 1033 } else { 1034 (void)gp->scr_cursor(sp, &oldy, &oldx); 1035 while (cnt--) { 1036 (void)gp->scr_move(sp, LASTLINE(sp) - 1, 0); 1037 (void)gp->scr_deleteln(sp); 1038 (void)gp->scr_move(sp, oldy, oldx); 1039 (void)gp->scr_insertln(sp); 1040 } 1041 } 1042 return (0); 1043 } 1044 1045 /* 1046 * vs_sm_next -- 1047 * Fill in the next entry in the SMAP. 1048 * 1049 * PUBLIC: int vs_sm_next __P((SCR *, SMAP *, SMAP *)); 1050 */ 1051 int 1052 vs_sm_next(sp, p, t) 1053 SCR *sp; 1054 SMAP *p, *t; 1055 { 1056 size_t lcnt; 1057 1058 SMAP_FLUSH(t); 1059 if (O_ISSET(sp, O_LEFTRIGHT)) { 1060 t->lno = p->lno + 1; 1061 t->coff = p->coff; 1062 } else { 1063 lcnt = vs_screens(sp, p->lno, NULL); 1064 if (lcnt == p->soff) { 1065 t->lno = p->lno + 1; 1066 t->soff = 1; 1067 } else { 1068 t->lno = p->lno; 1069 t->soff = p->soff + 1; 1070 } 1071 } 1072 return (0); 1073 } 1074 1075 /* 1076 * vs_sm_prev -- 1077 * Fill in the previous entry in the SMAP. 1078 * 1079 * PUBLIC: int vs_sm_prev __P((SCR *, SMAP *, SMAP *)); 1080 */ 1081 int 1082 vs_sm_prev(sp, p, t) 1083 SCR *sp; 1084 SMAP *p, *t; 1085 { 1086 SMAP_FLUSH(t); 1087 if (O_ISSET(sp, O_LEFTRIGHT)) { 1088 t->lno = p->lno - 1; 1089 t->coff = p->coff; 1090 } else { 1091 if (p->soff != 1) { 1092 t->lno = p->lno; 1093 t->soff = p->soff - 1; 1094 } else { 1095 t->lno = p->lno - 1; 1096 t->soff = vs_screens(sp, t->lno, NULL); 1097 } 1098 } 1099 return (t->lno == 0); 1100 } 1101 1102 /* 1103 * vs_sm_cursor -- 1104 * Return the SMAP entry referenced by the cursor. 1105 * 1106 * PUBLIC: int vs_sm_cursor __P((SCR *, SMAP **)); 1107 */ 1108 int 1109 vs_sm_cursor(sp, smpp) 1110 SCR *sp; 1111 SMAP **smpp; 1112 { 1113 SMAP *p; 1114 1115 /* See if the cursor is not in the map. */ 1116 if (sp->lno < HMAP->lno || sp->lno > TMAP->lno) 1117 return (1); 1118 1119 /* Find the first occurence of the line. */ 1120 for (p = HMAP; p->lno != sp->lno; ++p); 1121 1122 /* Fill in the map information until we find the right line. */ 1123 for (; p <= TMAP; ++p) { 1124 /* Short lines are common and easy to detect. */ 1125 if (p != TMAP && (p + 1)->lno != p->lno) { 1126 *smpp = p; 1127 return (0); 1128 } 1129 if (!SMAP_CACHE(p) && vs_line(sp, p, NULL, NULL)) 1130 return (1); 1131 if (p->c_eboff >= sp->cno) { 1132 *smpp = p; 1133 return (0); 1134 } 1135 } 1136 1137 /* It was past the end of the map after all. */ 1138 return (1); 1139 } 1140 1141 /* 1142 * vs_sm_position -- 1143 * Return the line/column of the top, middle or last line on the screen. 1144 * (The vi H, M and L commands.) Here because only the screen routines 1145 * know what's really out there. 1146 * 1147 * PUBLIC: int vs_sm_position __P((SCR *, MARK *, u_long, pos_t)); 1148 */ 1149 int 1150 vs_sm_position(sp, rp, cnt, pos) 1151 SCR *sp; 1152 MARK *rp; 1153 u_long cnt; 1154 pos_t pos; 1155 { 1156 SMAP *smp; 1157 recno_t last; 1158 1159 switch (pos) { 1160 case P_TOP: 1161 /* 1162 * !!! 1163 * Historically, an invalid count to the H command failed. 1164 * We do nothing special here, just making sure that H in 1165 * an empty screen works. 1166 */ 1167 if (cnt > TMAP - HMAP) 1168 goto sof; 1169 smp = HMAP + cnt; 1170 if (cnt && !db_exist(sp, smp->lno)) { 1171 sof: msgq(sp, M_BERR, "220|Movement past the end-of-screen"); 1172 return (1); 1173 } 1174 break; 1175 case P_MIDDLE: 1176 /* 1177 * !!! 1178 * Historically, a count to the M command was ignored. 1179 * If the screen isn't filled, find the middle of what's 1180 * real and move there. 1181 */ 1182 if (!db_exist(sp, TMAP->lno)) { 1183 if (db_last(sp, &last)) 1184 return (1); 1185 for (smp = TMAP; smp->lno > last && smp > HMAP; --smp); 1186 if (smp > HMAP) 1187 smp -= (smp - HMAP) / 2; 1188 } else 1189 smp = (HMAP + (TMAP - HMAP) / 2) + cnt; 1190 break; 1191 case P_BOTTOM: 1192 /* 1193 * !!! 1194 * Historically, an invalid count to the L command failed. 1195 * If the screen isn't filled, find the bottom of what's 1196 * real and try to offset from there. 1197 */ 1198 if (cnt > TMAP - HMAP) 1199 goto eof; 1200 smp = TMAP - cnt; 1201 if (!db_exist(sp, smp->lno)) { 1202 if (db_last(sp, &last)) 1203 return (1); 1204 for (; smp->lno > last && smp > HMAP; --smp); 1205 if (cnt > smp - HMAP) { 1206 eof: msgq(sp, M_BERR, 1207 "221|Movement past the beginning-of-screen"); 1208 return (1); 1209 } 1210 smp -= cnt; 1211 } 1212 break; 1213 default: 1214 abort(); 1215 } 1216 1217 /* Make sure that the cached information is valid. */ 1218 if (!SMAP_CACHE(smp) && vs_line(sp, smp, NULL, NULL)) 1219 return (1); 1220 rp->lno = smp->lno; 1221 rp->cno = smp->c_sboff; 1222 1223 return (0); 1224 } 1225 1226 /* 1227 * vs_sm_nlines -- 1228 * Return the number of screen lines from an SMAP entry to the 1229 * start of some file line, less than a maximum value. 1230 * 1231 * PUBLIC: recno_t vs_sm_nlines __P((SCR *, SMAP *, recno_t, size_t)); 1232 */ 1233 recno_t 1234 vs_sm_nlines(sp, from_sp, to_lno, max) 1235 SCR *sp; 1236 SMAP *from_sp; 1237 recno_t to_lno; 1238 size_t max; 1239 { 1240 recno_t lno, lcnt; 1241 1242 if (O_ISSET(sp, O_LEFTRIGHT)) 1243 return (from_sp->lno > to_lno ? 1244 from_sp->lno - to_lno : to_lno - from_sp->lno); 1245 1246 if (from_sp->lno == to_lno) 1247 return (from_sp->soff - 1); 1248 1249 if (from_sp->lno > to_lno) { 1250 lcnt = from_sp->soff - 1; /* Correct for off-by-one. */ 1251 for (lno = from_sp->lno; --lno >= to_lno && lcnt <= max;) 1252 lcnt += vs_screens(sp, lno, NULL); 1253 } else { 1254 lno = from_sp->lno; 1255 lcnt = (vs_screens(sp, lno, NULL) - from_sp->soff) + 1; 1256 for (; ++lno < to_lno && lcnt <= max;) 1257 lcnt += vs_screens(sp, lno, NULL); 1258 } 1259 return (lcnt); 1260 } 1261