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