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