1 /* $OpenBSD: line.c,v 1.2 2001/01/29 01:58:02 niklas Exp $ */ 2 3 /* 4 * Copyright (c) 1984,1985,1989,1994,1995 Mark Nudelman 5 * All rights reserved. 6 * 7 * Redistribution and use in source and binary forms, with or without 8 * modification, are permitted provided that the following conditions 9 * are met: 10 * 1. Redistributions of source code must retain the above copyright 11 * notice, this list of conditions and the following disclaimer. 12 * 2. Redistributions in binary form must reproduce the above copyright 13 * notice in the documentation and/or other materials provided with 14 * the distribution. 15 * 16 * THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY 17 * EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE 18 * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR 19 * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE AUTHOR BE LIABLE 20 * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR 21 * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT 22 * OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR 23 * BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, 24 * WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE 25 * OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN 26 * IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. 27 */ 28 29 30 /* 31 * Routines to manipulate the "line buffer". 32 * The line buffer holds a line of output as it is being built 33 * in preparation for output to the screen. 34 */ 35 36 #include "less.h" 37 38 public char linebuf[1024]; /* Buffer which holds the current output line */ 39 public int size_linebuf = sizeof(linebuf); 40 41 static char attr[1024]; /* Extension of linebuf to hold attributes */ 42 static int curr; /* Index into linebuf */ 43 static int column; /* Printable length, accounting for 44 backspaces, etc. */ 45 static int lno_indent; /* Number of chars used for line number */ 46 static int overstrike; /* Next char should overstrike previous char */ 47 static int is_null_line; /* There is no current line */ 48 static char pendc; 49 static POSITION pendpos; 50 51 static int do_append(); 52 53 extern int bs_mode; 54 extern int tabstop; 55 extern int linenums; 56 extern int ctldisp; 57 extern int twiddle; 58 extern int binattr; 59 extern int auto_wrap, ignaw; 60 extern int bo_s_width, bo_e_width; 61 extern int ul_s_width, ul_e_width; 62 extern int bl_s_width, bl_e_width; 63 extern int so_s_width, so_e_width; 64 extern int sc_width, sc_height; 65 66 /* 67 * Rewind the line buffer. 68 */ 69 public void 70 prewind() 71 { 72 curr = 0; 73 column = 0; 74 overstrike = 0; 75 is_null_line = 0; 76 lno_indent = 0; 77 pendc = '\0'; 78 } 79 80 /* 81 * Insert the line number (of the given position) into the line buffer. 82 */ 83 public void 84 plinenum(pos) 85 POSITION pos; 86 { 87 register int lno; 88 register int i; 89 register int n; 90 91 /* 92 * We display the line number at the start of each line 93 * only if the -N option is set. 94 */ 95 if (linenums != OPT_ONPLUS) 96 return; 97 98 /* 99 * Get the line number and put it in the current line. 100 * {{ Note: since find_linenum calls forw_raw_line, 101 * it may seek in the input file, requiring the caller 102 * of plinenum to re-seek if necessary. }} 103 */ 104 lno = find_linenum(pos); 105 106 sprintf(&linebuf[curr], "%6d", lno); 107 n = strlen(&linebuf[curr]); 108 column += n; 109 for (i = 0; i < n; i++) 110 attr[curr++] = 0; 111 112 /* 113 * Append enough spaces to bring us to the next tab stop. 114 * {{ We could avoid this at the cost of adding some 115 * complication to the tab stop logic in pappend(). }} 116 */ 117 if (tabstop == 0) 118 tabstop = 1; 119 do 120 { 121 linebuf[curr] = ' '; 122 attr[curr++] = AT_NORMAL; 123 column++; 124 } while ((column % tabstop) != 0); 125 lno_indent = column; 126 } 127 128 /* 129 * Return the printing width of the start (enter) sequence 130 * for a given character attribute. 131 */ 132 int 133 attr_swidth(a) 134 int a; 135 { 136 switch (a) 137 { 138 case AT_BOLD: return (bo_s_width); 139 case AT_UNDERLINE: return (ul_s_width); 140 case AT_BLINK: return (bl_s_width); 141 case AT_STANDOUT: return (so_s_width); 142 } 143 return (0); 144 } 145 146 /* 147 * Return the printing width of the end (exit) sequence 148 * for a given character attribute. 149 */ 150 int 151 attr_ewidth(a) 152 int a; 153 { 154 switch (a) 155 { 156 case AT_BOLD: return (bo_e_width); 157 case AT_UNDERLINE: return (ul_e_width); 158 case AT_BLINK: return (bl_e_width); 159 case AT_STANDOUT: return (so_e_width); 160 } 161 return (0); 162 } 163 164 /* 165 * Return the printing width of a given character and attribute, 166 * if the character were added to the current position in the line buffer. 167 * Adding a character with a given attribute may cause an enter or exit 168 * attribute sequence to be inserted, so this must be taken into account. 169 */ 170 static int 171 pwidth(c, a) 172 int c; 173 int a; 174 { 175 register int w; 176 177 if (c == '\b') 178 /* 179 * Backspace moves backwards one position. 180 */ 181 return (-1); 182 183 if (control_char(c)) 184 /* 185 * Control characters do unpredicatable things, 186 * so we don't even try to guess; say it doesn't move. 187 * This can only happen if the -r flag is in effect. 188 */ 189 return (0); 190 191 /* 192 * Other characters take one space, 193 * plus the width of any attribute enter/exit sequence. 194 */ 195 w = 1; 196 if (curr > 0 && attr[curr-1] != a) 197 w += attr_ewidth(attr[curr-1]); 198 if (a && (curr == 0 || attr[curr-1] != a)) 199 w += attr_swidth(a); 200 return (w); 201 } 202 203 /* 204 * Delete the previous character in the line buffer. 205 */ 206 static void 207 backc() 208 { 209 curr--; 210 column -= pwidth(linebuf[curr], attr[curr]); 211 } 212 213 /* 214 * Append a character and attribute to the line buffer. 215 */ 216 static int 217 storec(c, a, pos) 218 int c; 219 int a; 220 POSITION pos; 221 { 222 register int w; 223 224 #if HILITE_SEARCH 225 if (is_hilited(pos, pos+1, 0)) 226 /* 227 * This character should be highlighted. 228 * Override the attribute passed in. 229 */ 230 a = AT_STANDOUT; 231 #endif 232 w = pwidth(c, a); 233 if (ctldisp > 0 && column + w + attr_ewidth(a) > sc_width) 234 /* 235 * Won't fit on screen. 236 */ 237 return (1); 238 239 if (curr >= sizeof(linebuf)-2) 240 /* 241 * Won't fit in line buffer. 242 */ 243 return (1); 244 245 /* 246 * Special handling for "magic cookie" terminals. 247 * If an attribute enter/exit sequence has a printing width > 0, 248 * and the sequence is adjacent to a space, delete the space. 249 * We just mark the space as invisible, to avoid having too 250 * many spaces deleted. 251 * {{ Note that even if the attribute width is > 1, we 252 * delete only one space. It's not worth trying to do more. 253 * It's hardly worth doing this much. }} 254 */ 255 if (curr > 0 && a != AT_NORMAL && 256 linebuf[curr-1] == ' ' && attr[curr-1] == AT_NORMAL && 257 attr_swidth(a) > 0) 258 { 259 /* 260 * We are about to append an enter-attribute sequence 261 * just after a space. Delete the space. 262 */ 263 attr[curr-1] = AT_INVIS; 264 column--; 265 } else if (curr > 0 && attr[curr-1] != AT_NORMAL && 266 attr[curr-1] != AT_INVIS && c == ' ' && a == AT_NORMAL && 267 attr_ewidth(attr[curr-1]) > 0) 268 { 269 /* 270 * We are about to append a space just after an 271 * exit-attribute sequence. Delete the space. 272 */ 273 a = AT_INVIS; 274 column--; 275 } 276 /* End of magic cookie handling. */ 277 278 linebuf[curr] = c; 279 attr[curr] = a; 280 column += w; 281 return (0); 282 } 283 284 /* 285 * Append a character to the line buffer. 286 * Expand tabs into spaces, handle underlining, boldfacing, etc. 287 * Returns 0 if ok, 1 if couldn't fit in buffer. 288 */ 289 public int 290 pappend(c, pos) 291 register int c; 292 POSITION pos; 293 { 294 if (pendc) 295 { 296 if (do_append(pendc, pendpos)) 297 /* 298 * Oops. We've probably lost the char which 299 * was in pendc, since caller won't back up. 300 */ 301 return (1); 302 pendc = '\0'; 303 } 304 305 if (c == '\r' && bs_mode == BS_SPECIAL) 306 { 307 /* 308 * Don't put the CR into the buffer until we see 309 * the next char. If the next char is a newline, 310 * discard the CR. 311 */ 312 pendc = c; 313 pendpos = pos; 314 return (0); 315 } 316 317 return (do_append(c, pos)); 318 } 319 320 static int 321 do_append(c, pos) 322 int c; 323 POSITION pos; 324 { 325 register char *s; 326 register int a; 327 328 #define STOREC(c,a) \ 329 if (storec((c),(a),pos)) return (1); else curr++ 330 331 if (overstrike) 332 { 333 /* 334 * Overstrike the character at the current position 335 * in the line buffer. This will cause either 336 * underline (if a "_" is overstruck), 337 * bold (if an identical character is overstruck), 338 * or just deletion of the character in the buffer. 339 */ 340 overstrike = 0; 341 if ((char)c == linebuf[curr]) 342 STOREC(linebuf[curr], AT_BOLD); 343 else if (c == '_') 344 STOREC(linebuf[curr], AT_UNDERLINE); 345 else if (linebuf[curr] == '_') 346 STOREC(c, AT_UNDERLINE); 347 else if (control_char(c)) 348 goto do_control_char; 349 else 350 STOREC(c, AT_NORMAL); 351 } else if (c == '\b') 352 { 353 switch (bs_mode) 354 { 355 case BS_NORMAL: 356 STOREC(c, AT_NORMAL); 357 break; 358 case BS_CONTROL: 359 goto do_control_char; 360 case BS_SPECIAL: 361 if (curr == 0) 362 break; 363 backc(); 364 overstrike = 1; 365 break; 366 } 367 } else if (c == '\t') 368 { 369 /* 370 * Expand a tab into spaces. 371 */ 372 if (tabstop == 0) 373 tabstop = 1; 374 do 375 { 376 STOREC(' ', AT_NORMAL); 377 } while ((column % tabstop) != 0); 378 } else if (control_char(c)) 379 { 380 do_control_char: 381 if (ctldisp == 0) 382 { 383 /* 384 * Output as a normal character. 385 */ 386 STOREC(c, AT_NORMAL); 387 } else 388 { 389 /* 390 * Convert to printable representation. 391 */ 392 s = prchar(c); 393 a = binattr; 394 395 /* 396 * Make sure we can get the entire representation 397 * of the character on this line. 398 */ 399 if (column + (int) strlen(s) + 400 attr_swidth(a) + attr_ewidth(a) > sc_width) 401 return (1); 402 403 for ( ; *s != 0; s++) 404 STOREC(*s, a); 405 } 406 } else 407 { 408 STOREC(c, AT_NORMAL); 409 } 410 411 return (0); 412 } 413 414 /* 415 * Terminate the line in the line buffer. 416 */ 417 public void 418 pdone(endline) 419 int endline; 420 { 421 if (pendc && (pendc != '\r' || !endline)) 422 /* 423 * If we had a pending character, put it in the buffer. 424 * But discard a pending CR if we are at end of line 425 * (that is, discard the CR in a CR/LF sequence). 426 */ 427 (void) do_append(pendc, pendpos); 428 429 /* 430 * Add a newline if necessary, 431 * and append a '\0' to the end of the line. 432 */ 433 if (column < sc_width || !auto_wrap || ignaw || ctldisp == 0) 434 { 435 linebuf[curr] = '\n'; 436 attr[curr] = AT_NORMAL; 437 curr++; 438 } 439 linebuf[curr] = '\0'; 440 attr[curr] = AT_NORMAL; 441 } 442 443 /* 444 * Get a character from the current line. 445 * Return the character as the function return value, 446 * and the character attribute in *ap. 447 */ 448 public int 449 gline(i, ap) 450 register int i; 451 register int *ap; 452 { 453 char *s; 454 455 if (is_null_line) 456 { 457 /* 458 * If there is no current line, we pretend the line is 459 * either "~" or "", depending on the "twiddle" flag. 460 */ 461 *ap = AT_NORMAL; 462 s = (twiddle) ? "~\n" : "\n"; 463 return (s[i]); 464 } 465 466 *ap = attr[i]; 467 return (linebuf[i] & 0377); 468 } 469 470 /* 471 * Indicate that there is no current line. 472 */ 473 public void 474 null_line() 475 { 476 is_null_line = 1; 477 } 478 479 #if 1 480 /* 481 * Analogous to forw_line(), but deals with "raw lines": 482 * lines which are not split for screen width. 483 * {{ This is supposed to be more efficient than forw_line(). }} 484 */ 485 public POSITION 486 forw_raw_line(curr_pos, linep) 487 POSITION curr_pos; 488 char **linep; 489 { 490 register char *p; 491 register int c; 492 POSITION new_pos; 493 494 if (curr_pos == NULL_POSITION || ch_seek(curr_pos) || 495 (c = ch_forw_get()) == EOI) 496 return (NULL_POSITION); 497 498 p = linebuf; 499 500 for (;;) 501 { 502 if (c == '\n' || c == EOI) 503 { 504 new_pos = ch_tell(); 505 break; 506 } 507 if (p >= &linebuf[sizeof(linebuf)-1]) 508 { 509 /* 510 * Overflowed the input buffer. 511 * Pretend the line ended here. 512 * {{ The line buffer is supposed to be big 513 * enough that this never happens. }} 514 */ 515 new_pos = ch_tell() - 1; 516 break; 517 } 518 *p++ = c; 519 c = ch_forw_get(); 520 } 521 *p = '\0'; 522 if (linep != NULL) 523 *linep = linebuf; 524 return (new_pos); 525 } 526 527 /* 528 * Analogous to back_line(), but deals with "raw lines". 529 * {{ This is supposed to be more efficient than back_line(). }} 530 */ 531 public POSITION 532 back_raw_line(curr_pos, linep) 533 POSITION curr_pos; 534 char **linep; 535 { 536 register char *p; 537 register int c; 538 POSITION new_pos; 539 540 if (curr_pos == NULL_POSITION || curr_pos <= ch_zero() || 541 ch_seek(curr_pos-1)) 542 return (NULL_POSITION); 543 544 p = &linebuf[sizeof(linebuf)]; 545 *--p = '\0'; 546 547 for (;;) 548 { 549 c = ch_back_get(); 550 if (c == '\n') 551 { 552 /* 553 * This is the newline ending the previous line. 554 * We have hit the beginning of the line. 555 */ 556 new_pos = ch_tell() + 1; 557 break; 558 } 559 if (c == EOI) 560 { 561 /* 562 * We have hit the beginning of the file. 563 * This must be the first line in the file. 564 * This must, of course, be the beginning of the line. 565 */ 566 new_pos = ch_zero(); 567 break; 568 } 569 if (p <= linebuf) 570 { 571 /* 572 * Overflowed the input buffer. 573 * Pretend the line ended here. 574 */ 575 new_pos = ch_tell() + 1; 576 break; 577 } 578 *--p = c; 579 } 580 if (linep != NULL) 581 *linep = p; 582 return (new_pos); 583 } 584 #endif 585