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