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