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