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 } 187 188 (void)gp->scr_clrtoeol(sp); 189 (void)gp->scr_move(sp, oldy, oldx); 190 return (0); 191 } 192 193 /* If we shortened this line in another screen, the cursor 194 * position may have fallen off. 195 */ 196 if (sp->lno == smp->lno && sp->cno >= len) 197 sp->cno = len - 1; 198 199 /* 200 * If we just wrote this or a previous line, we cached the starting 201 * and ending positions of that line. The way it works is we keep 202 * information about the lines displayed in the SMAP. If we're 203 * painting the screen in the forward direction, this saves us from 204 * reformatting the physical line for every line on the screen. This 205 * wins big on binary files with 10K lines. 206 * 207 * Test for the first screen of the line, then the current screen line, 208 * then the line behind us, then do the hard work. Note, it doesn't 209 * do us any good to have a line in front of us -- it would be really 210 * hard to try and figure out tabs in the reverse direction, i.e. how 211 * many spaces a tab takes up in the reverse direction depends on 212 * what characters preceded it. 213 * 214 * Test for the first screen of the line. 215 */ 216 if (skip_cols == 0) { 217 smp->c_sboff = offset_in_line = 0; 218 smp->c_scoff = offset_in_char = 0; 219 p = &p[offset_in_line]; 220 goto display; 221 } 222 223 /* Test to see if we've seen this exact line before. */ 224 if (is_cached) { 225 offset_in_line = smp->c_sboff; 226 offset_in_char = smp->c_scoff; 227 p = &p[offset_in_line]; 228 229 /* Set cols_per_screen to 2nd and later line length. */ 230 if (O_ISSET(sp, O_LEFTRIGHT) || skip_cols > cols_per_screen) 231 cols_per_screen = sp->cols; 232 goto display; 233 } 234 235 /* Test to see if we saw an earlier part of this line before. */ 236 if (smp != HMAP && 237 SMAP_CACHE(tsmp = smp - 1) && tsmp->lno == smp->lno) { 238 if (tsmp->c_eclen != tsmp->c_ecsize) { 239 offset_in_line = tsmp->c_eboff; 240 offset_in_char = tsmp->c_eclen; 241 } else { 242 offset_in_line = tsmp->c_eboff + 1; 243 offset_in_char = 0; 244 } 245 246 /* Put starting info for this line in the cache. */ 247 smp->c_sboff = offset_in_line; 248 smp->c_scoff = offset_in_char; 249 p = &p[offset_in_line]; 250 251 /* Set cols_per_screen to 2nd and later line length. */ 252 if (O_ISSET(sp, O_LEFTRIGHT) || skip_cols > cols_per_screen) 253 cols_per_screen = sp->cols; 254 goto display; 255 } 256 257 scno = 0; 258 offset_in_line = 0; 259 offset_in_char = 0; 260 261 /* Do it the hard way, for leftright scrolling screens. */ 262 if (O_ISSET(sp, O_LEFTRIGHT)) { 263 for (; offset_in_line < len; ++offset_in_line) { 264 chlen = (ch = *p++) == '\t' && !list_tab ? 265 TAB_OFF(scno) : KEY_COL(sp, ch); 266 if ((scno += chlen) >= skip_cols) 267 break; 268 } 269 270 /* Set cols_per_screen to 2nd and later line length. */ 271 cols_per_screen = sp->cols; 272 273 /* Put starting info for this line in the cache. */ 274 if (offset_in_line >= len) { 275 smp->c_sboff = offset_in_line; 276 smp->c_scoff = 255; 277 } else if (scno != skip_cols) { 278 smp->c_sboff = offset_in_line; 279 smp->c_scoff = 280 offset_in_char = chlen - (scno - skip_cols); 281 --p; 282 } else { 283 smp->c_sboff = ++offset_in_line; 284 smp->c_scoff = 0; 285 } 286 } 287 288 /* Do it the hard way, for historic line-folding screens. */ 289 else { 290 for (; offset_in_line < len; ++offset_in_line) { 291 chlen = (ch = *p++) == '\t' && !list_tab ? 292 TAB_OFF(scno) : KEY_COL(sp, ch); 293 if ((scno += chlen) < cols_per_screen) 294 continue; 295 scno -= cols_per_screen; 296 297 /* Set cols_per_screen to 2nd and later line length. */ 298 cols_per_screen = sp->cols; 299 300 /* 301 * If crossed the last skipped screen boundary, start 302 * displaying the characters. 303 */ 304 if (--skip_screens == 0) 305 break; 306 } 307 308 /* Put starting info for this line in the cache. */ 309 if (scno != 0) { 310 smp->c_sboff = offset_in_line; 311 smp->c_scoff = offset_in_char = chlen - scno; 312 --p; 313 } else { 314 smp->c_sboff = ++offset_in_line; 315 smp->c_scoff = 0; 316 } 317 } 318 319 display: 320 /* 321 * Set the number of characters to skip before reaching the cursor 322 * character. Offset by 1 and use 0 as a flag value. Vs_line is 323 * called repeatedly with a valid pointer to a cursor position. 324 * Don't fill anything in unless it's the right line and the right 325 * character, and the right part of the character... 326 */ 327 if (yp == NULL || 328 smp->lno != sp->lno || sp->cno < offset_in_line || 329 offset_in_line + cols_per_screen < sp->cno) { 330 cno_cnt = 0; 331 /* If the line is on the screen, quit. */ 332 if (is_cached || no_draw) 333 goto ret1; 334 } else 335 cno_cnt = (sp->cno - offset_in_line) + 1; 336 337 /* This is the loop that actually displays characters. */ 338 ecbp = (cbp = cbuf) + SIZE(cbuf) - 1; 339 for (is_partial = 0, scno = 0; 340 offset_in_line < len; ++offset_in_line, offset_in_char = 0) { 341 if ((ch = *p++) == '\t' && !list_tab) { 342 scno += chlen = TAB_OFF(scno) - offset_in_char; 343 is_tab = 1; 344 } else { 345 scno += chlen = KEY_COL(sp, ch) - offset_in_char; 346 is_tab = 0; 347 } 348 349 /* 350 * Only display up to the right-hand column. Set a flag if 351 * the entire character wasn't displayed for use in setting 352 * the cursor. If reached the end of the line, set the cache 353 * info for the screen. Don't worry about there not being 354 * characters to display on the next screen, its lno/off won't 355 * match up in that case. 356 */ 357 if (scno >= cols_per_screen) { 358 if (is_tab == 1) { 359 chlen -= scno - cols_per_screen; 360 smp->c_ecsize = smp->c_eclen = chlen; 361 scno = cols_per_screen; 362 } else { 363 smp->c_ecsize = chlen; 364 chlen -= scno - cols_per_screen; 365 smp->c_eclen = chlen; 366 367 if (scno > cols_per_screen) 368 is_partial = 1; 369 } 370 smp->c_eboff = offset_in_line; 371 372 /* Terminate the loop. */ 373 offset_in_line = len; 374 } 375 376 /* 377 * If the caller wants the cursor value, and this was the 378 * cursor character, set the value. There are two ways to 379 * put the cursor on a character -- if it's normal display 380 * mode, it goes on the last column of the character. If 381 * it's input mode, it goes on the first. In normal mode, 382 * set the cursor only if the entire character was displayed. 383 */ 384 if (cno_cnt && 385 --cno_cnt == 0 && (F_ISSET(sp, SC_TINPUT) || !is_partial)) { 386 *yp = smp - HMAP; 387 if (F_ISSET(sp, SC_TINPUT)) 388 if (is_partial) 389 *xp = scno - smp->c_ecsize; 390 else 391 *xp = scno - chlen; 392 else 393 *xp = scno - 1; 394 if (O_ISSET(sp, O_NUMBER) && 395 !F_ISSET(sp, SC_TINPUT_INFO) && skip_cols == 0) 396 *xp += O_NUMBER_LENGTH; 397 398 /* If the line is on the screen, quit. */ 399 if (is_cached || no_draw) 400 goto ret1; 401 } 402 403 /* If the line is on the screen, don't display anything. */ 404 if (is_cached || no_draw) 405 continue; 406 407 #define FLUSH do { \ 408 *cbp = '\0'; \ 409 (void)gp->scr_waddstr(sp, cbuf, cbp - cbuf); \ 410 cbp = cbuf; \ 411 } while (0) 412 /* 413 * Display the character. We do tab expansion here because 414 * the screen interface doesn't have any way to set the tab 415 * length. Note, it's theoretically possible for chlen to 416 * be larger than cbuf, if the user set a impossibly large 417 * tabstop. 418 */ 419 if (is_tab) 420 while (chlen--) { 421 if (cbp >= ecbp) 422 FLUSH; 423 *cbp++ = TABCH; 424 } 425 else { 426 if (cbp + chlen >= ecbp) 427 FLUSH; 428 429 /* don't display half a wide character */ 430 if (is_partial && CHAR_WIDTH(sp, ch) > 1) { 431 *cbp++ = ' '; 432 break; 433 } 434 435 if (KEY_NEEDSWIDE(sp, ch)) 436 *cbp++ = ch; 437 else 438 for (kp = (u_char *) 439 KEY_NAME(sp, ch) + offset_in_char; 440 chlen--;) 441 *cbp++ = *kp++; 442 } 443 } 444 445 if (scno < cols_per_screen) { 446 /* If didn't paint the whole line, update the cache. */ 447 smp->c_ecsize = smp->c_eclen = KEY_COL(sp, ch); 448 smp->c_eboff = len - 1; 449 450 /* 451 * If not the info/mode line, and O_LIST set, and at the 452 * end of the line, and the line ended on this screen, 453 * add a trailing $. 454 */ 455 if (list_dollar) { 456 ++scno; 457 458 chlen = KEY_LEN(sp, '$'); 459 if (cbp + chlen >= ecbp) 460 FLUSH; 461 for (kp = (u_char *) 462 KEY_NAME(sp, '$'); chlen--;) 463 *cbp++ = *kp++; 464 } 465 466 /* If still didn't paint the whole line, clear the rest. */ 467 if (scno < cols_per_screen) 468 (void)gp->scr_clrtoeol(sp); 469 } 470 471 /* Flush any buffered characters. */ 472 if (cbp > cbuf) 473 FLUSH; 474 475 ret1: (void)gp->scr_move(sp, oldy, oldx); 476 return (0); 477 } 478 479 /* 480 * vs_number -- 481 * Repaint the numbers on all the lines. 482 * 483 * PUBLIC: int vs_number(SCR *); 484 */ 485 int 486 vs_number(SCR *sp) 487 { 488 GS *gp; 489 SMAP *smp; 490 VI_PRIVATE *vip; 491 size_t len, oldy, oldx; 492 int exist; 493 char nbuf[10]; 494 495 gp = sp->gp; 496 vip = VIP(sp); 497 498 /* No reason to do anything if we're in input mode on the info line. */ 499 if (F_ISSET(sp, SC_TINPUT_INFO)) 500 return (0); 501 502 /* 503 * Try and avoid getting the last line in the file, by getting the 504 * line after the last line in the screen -- if it exists, we know 505 * we have to to number all the lines in the screen. Get the one 506 * after the last instead of the last, so that the info line doesn't 507 * fool us. (The problem is that file_lline will lie, and tell us 508 * that the info line is the last line in the file.) If that test 509 * fails, we have to check each line for existence. 510 */ 511 exist = db_exist(sp, TMAP->lno + 1); 512 513 (void)gp->scr_cursor(sp, &oldy, &oldx); 514 for (smp = HMAP; smp <= TMAP; ++smp) { 515 /* Numbers are only displayed for the first screen line. */ 516 if (O_ISSET(sp, O_LEFTRIGHT)) { 517 if (smp->coff != 0) 518 continue; 519 } else 520 if (smp->soff != 1) 521 continue; 522 523 /* 524 * The first line of an empty file gets numbered, otherwise 525 * number any existing line. 526 */ 527 if (smp->lno != 1 && !exist && !db_exist(sp, smp->lno)) 528 break; 529 530 (void)gp->scr_move(sp, smp - HMAP, 0); 531 len = snprintf(nbuf, sizeof(nbuf), O_NUMBER_FMT, (u_long)smp->lno); 532 (void)gp->scr_addstr(sp, nbuf, len); 533 } 534 (void)gp->scr_move(sp, oldy, oldx); 535 return (0); 536 } 537