1 /*- 2 * Copyright (c) 1993, 1994 3 * The Regents of the University of California. All rights reserved. 4 * Copyright (c) 1992, 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 <string.h> 20 21 #include "../common/common.h" 22 #include "vi.h" 23 24 #ifdef VISIBLE_TAB_CHARS 25 #define TABCH '-' 26 #else 27 #define TABCH ' ' 28 #endif 29 30 /* 31 * vs_line -- 32 * Update one line on the screen. 33 * 34 * PUBLIC: int vs_line(SCR *, SMAP *, size_t *, size_t *); 35 */ 36 int 37 vs_line(SCR *sp, SMAP *smp, size_t *yp, size_t *xp) 38 { 39 u_char *kp; 40 GS *gp; 41 SMAP *tsmp; 42 size_t chlen = 0, cno_cnt, cols_per_screen, len, nlen; 43 size_t offset_in_char, offset_in_line, oldx, oldy; 44 size_t scno, skip_cols, skip_screens; 45 int dne, is_cached, is_partial, is_tab, no_draw; 46 int list_tab, list_dollar; 47 CHAR_T *p; 48 CHAR_T *cbp, *ecbp, cbuf[128]; 49 ARG_CHAR_T ch = '\0'; 50 51 #if defined(DEBUG) && 0 52 TRACE(sp, "vs_line: row %u: line: %u off: %u\n", 53 smp - HMAP, smp->lno, smp->off); 54 #endif 55 /* 56 * If ex modifies the screen after ex output is already on the screen, 57 * don't touch it -- we'll get scrolling wrong, at best. 58 */ 59 no_draw = 0; 60 if (!F_ISSET(sp, SC_TINPUT_INFO) && VIP(sp)->totalcount > 1) 61 no_draw = 1; 62 if (F_ISSET(sp, SC_SCR_EXWROTE) && smp - HMAP != LASTLINE(sp)) 63 no_draw = 1; 64 65 /* 66 * Assume that, if the cache entry for the line is filled in, the 67 * line is already on the screen, and all we need to do is return 68 * the cursor position. If the calling routine doesn't need the 69 * cursor position, we can just return. 70 */ 71 is_cached = SMAP_CACHE(smp); 72 if (yp == NULL && (is_cached || no_draw)) 73 return (0); 74 75 /* 76 * A nasty side effect of this routine is that it returns the screen 77 * position for the "current" character. Not pretty, but this is the 78 * only routine that really knows what's out there. 79 * 80 * Move to the line. This routine can be called by vs_sm_position(), 81 * which uses it to fill in the cache entry so it can figure out what 82 * the real contents of the screen are. Because of this, we have to 83 * return to whereever we started from. 84 */ 85 gp = sp->gp; 86 (void)gp->scr_cursor(sp, &oldy, &oldx); 87 (void)gp->scr_move(sp, smp - HMAP, 0); 88 89 /* Get the line. */ 90 dne = db_get(sp, smp->lno, 0, &p, &len); 91 92 /* 93 * Special case if we're printing the info/mode line. Skip printing 94 * the leading number, as well as other minor setup. The only time 95 * this code paints the mode line is when the user is entering text 96 * for a ":" command, so we can put the code here instead of dealing 97 * with the empty line logic below. This is a kludge, but it's pretty 98 * much confined to this module. 99 * 100 * Set the number of columns for this screen. 101 * Set the number of chars or screens to skip until a character is to 102 * be displayed. 103 */ 104 cols_per_screen = sp->cols; 105 if (O_ISSET(sp, O_LEFTRIGHT)) { 106 skip_screens = 0; 107 skip_cols = smp->coff; 108 } else { 109 skip_screens = smp->soff - 1; 110 skip_cols = skip_screens * cols_per_screen; 111 } 112 113 list_tab = O_ISSET(sp, O_LIST); 114 if (F_ISSET(sp, SC_TINPUT_INFO)) 115 list_dollar = 0; 116 else { 117 list_dollar = list_tab; 118 119 /* 120 * If O_NUMBER is set, the line doesn't exist and it's line 121 * number 1, i.e., an empty file, display the line number. 122 * 123 * If O_NUMBER is set, the line exists and the first character 124 * on the screen is the first character in the line, display 125 * the line number. 126 * 127 * !!! 128 * If O_NUMBER set, decrement the number of columns in the 129 * first screen. DO NOT CHANGE THIS -- IT'S RIGHT! The 130 * rest of the code expects this to reflect the number of 131 * columns in the first screen, regardless of the number of 132 * columns we're going to skip. 133 */ 134 if (O_ISSET(sp, O_NUMBER)) { 135 cols_per_screen -= O_NUMBER_LENGTH; 136 if ((!dne || smp->lno == 1) && skip_cols == 0) { 137 nlen = snprintf((char*)cbuf, 138 sizeof(cbuf), O_NUMBER_FMT, (u_long)smp->lno); 139 (void)gp->scr_addstr(sp, (char*)cbuf, nlen); 140 } 141 } 142 } 143 144 /* 145 * Special case non-existent lines and the first line of an empty 146 * file. In both cases, the cursor position is 0, but corrected 147 * as necessary for the O_NUMBER field, if it was displayed. 148 */ 149 if (dne || len == 0) { 150 /* Fill in the cursor. */ 151 if (yp != NULL && smp->lno == sp->lno) { 152 *yp = smp - HMAP; 153 *xp = sp->cols - cols_per_screen; 154 } 155 156 /* If the line is on the screen, quit. */ 157 if (is_cached || no_draw) 158 goto ret1; 159 160 /* Set line cache information. */ 161 smp->c_sboff = smp->c_eboff = 0; 162 smp->c_scoff = smp->c_eclen = 0; 163 164 /* 165 * Lots of special cases for empty lines, but they only apply 166 * if we're displaying the first screen of the line. 167 */ 168 if (skip_cols == 0) 169 if (dne) { 170 if (smp->lno == 1) { 171 if (list_dollar) { 172 ch = '$'; 173 goto empty; 174 } 175 } else { 176 ch = '~'; 177 goto empty; 178 } 179 } else 180 if (list_dollar) { 181 ch = '$'; 182 empty: (void)gp->scr_addstr(sp, 183 KEY_NAME(sp, ch), KEY_LEN(sp, ch)); 184 } 185 186 (void)gp->scr_clrtoeol(sp); 187 (void)gp->scr_move(sp, oldy, oldx); 188 return (0); 189 } 190 191 /* If we shortened this line in another screen, the cursor 192 * position may have fallen off. 193 */ 194 if (sp->lno == smp->lno && sp->cno >= len) 195 sp->cno = len - 1; 196 197 /* 198 * If we just wrote this or a previous line, we cached the starting 199 * and ending positions of that line. The way it works is we keep 200 * information about the lines displayed in the SMAP. If we're 201 * painting the screen in the forward direction, this saves us from 202 * reformatting the physical line for every line on the screen. This 203 * wins big on binary files with 10K lines. 204 * 205 * Test for the first screen of the line, then the current screen line, 206 * then the line behind us, then do the hard work. Note, it doesn't 207 * do us any good to have a line in front of us -- it would be really 208 * hard to try and figure out tabs in the reverse direction, i.e. how 209 * many spaces a tab takes up in the reverse direction depends on 210 * what characters preceded it. 211 * 212 * Test for the first screen of the line. 213 */ 214 if (skip_cols == 0) { 215 smp->c_sboff = offset_in_line = 0; 216 smp->c_scoff = offset_in_char = 0; 217 p = &p[offset_in_line]; 218 goto display; 219 } 220 221 /* Test to see if we've seen this exact line before. */ 222 if (is_cached) { 223 offset_in_line = smp->c_sboff; 224 offset_in_char = smp->c_scoff; 225 p = &p[offset_in_line]; 226 227 /* Set cols_per_screen to 2nd and later line length. */ 228 if (O_ISSET(sp, O_LEFTRIGHT) || skip_cols > cols_per_screen) 229 cols_per_screen = sp->cols; 230 goto display; 231 } 232 233 /* Test to see if we saw an earlier part of this line before. */ 234 if (smp != HMAP && 235 SMAP_CACHE(tsmp = smp - 1) && tsmp->lno == smp->lno) { 236 if (tsmp->c_eclen != tsmp->c_ecsize) { 237 offset_in_line = tsmp->c_eboff; 238 offset_in_char = tsmp->c_eclen; 239 } else { 240 offset_in_line = tsmp->c_eboff + 1; 241 offset_in_char = 0; 242 } 243 244 /* Put starting info for this line in the cache. */ 245 smp->c_sboff = offset_in_line; 246 smp->c_scoff = offset_in_char; 247 p = &p[offset_in_line]; 248 249 /* Set cols_per_screen to 2nd and later line length. */ 250 if (O_ISSET(sp, O_LEFTRIGHT) || skip_cols > cols_per_screen) 251 cols_per_screen = sp->cols; 252 goto display; 253 } 254 255 scno = 0; 256 offset_in_line = 0; 257 offset_in_char = 0; 258 259 /* Do it the hard way, for leftright scrolling screens. */ 260 if (O_ISSET(sp, O_LEFTRIGHT)) { 261 for (; offset_in_line < len; ++offset_in_line) { 262 chlen = (ch = *p++) == '\t' && !list_tab ? 263 TAB_OFF(scno) : KEY_COL(sp, ch); 264 if ((scno += chlen) >= skip_cols) 265 break; 266 } 267 268 /* Set cols_per_screen to 2nd and later line length. */ 269 cols_per_screen = sp->cols; 270 271 /* Put starting info for this line in the cache. */ 272 if (offset_in_line >= len) { 273 smp->c_sboff = offset_in_line; 274 smp->c_scoff = 255; 275 } else if (scno != skip_cols) { 276 smp->c_sboff = offset_in_line; 277 smp->c_scoff = 278 offset_in_char = chlen - (scno - skip_cols); 279 --p; 280 } else { 281 smp->c_sboff = ++offset_in_line; 282 smp->c_scoff = 0; 283 } 284 } 285 286 /* Do it the hard way, for historic line-folding screens. */ 287 else { 288 for (; offset_in_line < len; ++offset_in_line) { 289 chlen = (ch = *p++) == '\t' && !list_tab ? 290 TAB_OFF(scno) : KEY_COL(sp, ch); 291 if ((scno += chlen) < cols_per_screen) 292 continue; 293 scno -= cols_per_screen; 294 295 /* Set cols_per_screen to 2nd and later line length. */ 296 cols_per_screen = sp->cols; 297 298 /* 299 * If crossed the last skipped screen boundary, start 300 * displaying the characters. 301 */ 302 if (--skip_screens == 0) 303 break; 304 } 305 306 /* Put starting info for this line in the cache. */ 307 if (scno != 0) { 308 smp->c_sboff = offset_in_line; 309 smp->c_scoff = offset_in_char = chlen - scno; 310 --p; 311 } else { 312 smp->c_sboff = ++offset_in_line; 313 smp->c_scoff = 0; 314 } 315 } 316 317 display: 318 /* 319 * Set the number of characters to skip before reaching the cursor 320 * character. Offset by 1 and use 0 as a flag value. Vs_line is 321 * called repeatedly with a valid pointer to a cursor position. 322 * Don't fill anything in unless it's the right line and the right 323 * character, and the right part of the character... 324 */ 325 if (yp == NULL || 326 smp->lno != sp->lno || sp->cno < offset_in_line || 327 offset_in_line + cols_per_screen < sp->cno) { 328 cno_cnt = 0; 329 /* If the line is on the screen, quit. */ 330 if (is_cached || no_draw) 331 goto ret1; 332 } else 333 cno_cnt = (sp->cno - offset_in_line) + 1; 334 335 /* This is the loop that actually displays characters. */ 336 ecbp = (cbp = cbuf) + SIZE(cbuf) - 1; 337 for (is_partial = 0, scno = 0; 338 offset_in_line < len; ++offset_in_line, offset_in_char = 0) { 339 if ((ch = *p++) == '\t' && !list_tab) { 340 scno += chlen = TAB_OFF(scno) - offset_in_char; 341 is_tab = 1; 342 } else { 343 scno += chlen = KEY_COL(sp, ch) - offset_in_char; 344 is_tab = 0; 345 } 346 347 /* 348 * Only display up to the right-hand column. Set a flag if 349 * the entire character wasn't displayed for use in setting 350 * the cursor. If reached the end of the line, set the cache 351 * info for the screen. Don't worry about there not being 352 * characters to display on the next screen, its lno/off won't 353 * match up in that case. 354 */ 355 if (scno >= cols_per_screen) { 356 if (is_tab == 1) { 357 chlen -= scno - cols_per_screen; 358 smp->c_ecsize = smp->c_eclen = chlen; 359 scno = cols_per_screen; 360 } else { 361 smp->c_ecsize = chlen; 362 chlen -= scno - cols_per_screen; 363 smp->c_eclen = chlen; 364 365 if (scno > cols_per_screen) 366 is_partial = 1; 367 } 368 smp->c_eboff = offset_in_line; 369 370 /* Terminate the loop. */ 371 offset_in_line = len; 372 } 373 374 /* 375 * If the caller wants the cursor value, and this was the 376 * cursor character, set the value. There are two ways to 377 * put the cursor on a character -- if it's normal display 378 * mode, it goes on the last column of the character. If 379 * it's input mode, it goes on the first. In normal mode, 380 * set the cursor only if the entire character was displayed. 381 */ 382 if (cno_cnt && 383 --cno_cnt == 0 && (F_ISSET(sp, SC_TINPUT) || !is_partial)) { 384 *yp = smp - HMAP; 385 if (F_ISSET(sp, SC_TINPUT)) 386 if (is_partial) 387 *xp = scno - smp->c_ecsize; 388 else 389 *xp = scno - chlen; 390 else 391 *xp = scno - 1; 392 if (O_ISSET(sp, O_NUMBER) && 393 !F_ISSET(sp, SC_TINPUT_INFO) && skip_cols == 0) 394 *xp += O_NUMBER_LENGTH; 395 396 /* If the line is on the screen, quit. */ 397 if (is_cached || no_draw) 398 goto ret1; 399 } 400 401 /* If the line is on the screen, don't display anything. */ 402 if (is_cached || no_draw) 403 continue; 404 405 #define FLUSH { \ 406 *cbp = '\0'; \ 407 (void)gp->scr_waddstr(sp, cbuf, cbp - cbuf); \ 408 cbp = cbuf; \ 409 } 410 /* 411 * Display the character. We do tab expansion here because 412 * the screen interface doesn't have any way to set the tab 413 * length. Note, it's theoretically possible for chlen to 414 * be larger than cbuf, if the user set a impossibly large 415 * tabstop. 416 */ 417 if (is_tab) 418 while (chlen--) { 419 if (cbp >= ecbp) 420 FLUSH; 421 *cbp++ = TABCH; 422 } 423 else { 424 if (cbp + chlen >= ecbp) 425 FLUSH; 426 427 /* don't display half a wide character */ 428 if (is_partial && CHAR_WIDTH(sp, ch) > 1) { 429 *cbp++ = ' '; 430 break; 431 } 432 433 if (KEY_NEEDSWIDE(sp, ch)) 434 *cbp++ = ch; 435 else 436 for (kp = (u_char *) 437 KEY_NAME(sp, ch) + offset_in_char; 438 chlen--;) 439 *cbp++ = *kp++; 440 } 441 } 442 443 if (scno < cols_per_screen) { 444 /* If didn't paint the whole line, update the cache. */ 445 smp->c_ecsize = smp->c_eclen = KEY_COL(sp, ch); 446 smp->c_eboff = len - 1; 447 448 /* 449 * If not the info/mode line, and O_LIST set, and at the 450 * end of the line, and the line ended on this screen, 451 * add a trailing $. 452 */ 453 if (list_dollar) { 454 ++scno; 455 456 chlen = KEY_LEN(sp, '$'); 457 if (cbp + chlen >= ecbp) 458 FLUSH; 459 for (kp = (u_char *) 460 KEY_NAME(sp, '$'); chlen--;) 461 *cbp++ = *kp++; 462 } 463 464 /* If still didn't paint the whole line, clear the rest. */ 465 if (scno < cols_per_screen) 466 (void)gp->scr_clrtoeol(sp); 467 } 468 469 /* Flush any buffered characters. */ 470 if (cbp > cbuf) 471 FLUSH; 472 473 ret1: (void)gp->scr_move(sp, oldy, oldx); 474 return (0); 475 } 476 477 /* 478 * vs_number -- 479 * Repaint the numbers on all the lines. 480 * 481 * PUBLIC: int vs_number(SCR *); 482 */ 483 int 484 vs_number(SCR *sp) 485 { 486 GS *gp; 487 SMAP *smp; 488 VI_PRIVATE *vip; 489 size_t len, oldy, oldx; 490 int exist; 491 char nbuf[10]; 492 493 gp = sp->gp; 494 vip = VIP(sp); 495 496 /* No reason to do anything if we're in input mode on the info line. */ 497 if (F_ISSET(sp, SC_TINPUT_INFO)) 498 return (0); 499 500 /* 501 * Try and avoid getting the last line in the file, by getting the 502 * line after the last line in the screen -- if it exists, we know 503 * we have to to number all the lines in the screen. Get the one 504 * after the last instead of the last, so that the info line doesn't 505 * fool us. (The problem is that file_lline will lie, and tell us 506 * that the info line is the last line in the file.) If that test 507 * fails, we have to check each line for existence. 508 */ 509 exist = db_exist(sp, TMAP->lno + 1); 510 511 (void)gp->scr_cursor(sp, &oldy, &oldx); 512 for (smp = HMAP; smp <= TMAP; ++smp) { 513 /* Numbers are only displayed for the first screen line. */ 514 if (O_ISSET(sp, O_LEFTRIGHT)) { 515 if (smp->coff != 0) 516 continue; 517 } else 518 if (smp->soff != 1) 519 continue; 520 521 /* 522 * The first line of an empty file gets numbered, otherwise 523 * number any existing line. 524 */ 525 if (smp->lno != 1 && !exist && !db_exist(sp, smp->lno)) 526 break; 527 528 (void)gp->scr_move(sp, smp - HMAP, 0); 529 len = snprintf(nbuf, sizeof(nbuf), O_NUMBER_FMT, (u_long)smp->lno); 530 (void)gp->scr_addstr(sp, nbuf, len); 531 } 532 (void)gp->scr_move(sp, oldy, oldx); 533 return (0); 534 } 535