1 /* 2 * Copyright (c) 1988 Mark Nudleman 3 * Copyright (c) 1988 Regents of the University of California. 4 * All rights reserved. 5 * 6 * %sccs.include.redist.c% 7 */ 8 9 #ifndef lint 10 static char sccsid[] = "@(#)line.c 5.5 (Berkeley) 07/24/91"; 11 #endif /* not lint */ 12 13 /* 14 * Routines to manipulate the "line buffer". 15 * The line buffer holds a line of output as it is being built 16 * in preparation for output to the screen. 17 * We keep track of the PRINTABLE length of the line as it is being built. 18 */ 19 20 #include <sys/types.h> 21 #include <ctype.h> 22 #include <less.h> 23 24 static char linebuf[1024]; /* Buffer which holds the current output line */ 25 static char *curr; /* Pointer into linebuf */ 26 static int column; /* Printable length, accounting for 27 backspaces, etc. */ 28 /* 29 * A ridiculously complex state machine takes care of backspaces. The 30 * complexity arises from the attempt to deal with all cases, especially 31 * involving long lines with underlining, boldfacing or whatever. There 32 * are still some cases which will break it. 33 * 34 * There are four states: 35 * LN_NORMAL is the normal state (not in underline mode). 36 * LN_UNDERLINE means we are in underline mode. We expect to get 37 * either a sequence like "_\bX" or "X\b_" to continue 38 * underline mode, or anything else to end underline mode. 39 * LN_BOLDFACE means we are in boldface mode. We expect to get sequences 40 * like "X\bX\b...X\bX" to continue boldface mode, or anything 41 * else to end boldface mode. 42 * LN_UL_X means we are one character after LN_UNDERLINE 43 * (we have gotten the '_' in "_\bX" or the 'X' in "X\b_"). 44 * LN_UL_XB means we are one character after LN_UL_X 45 * (we have gotten the backspace in "_\bX" or "X\b_"; 46 * we expect one more ordinary character, 47 * which will put us back in state LN_UNDERLINE). 48 * LN_BO_X means we are one character after LN_BOLDFACE 49 * (we have gotten the 'X' in "X\bX"). 50 * LN_BO_XB means we are one character after LN_BO_X 51 * (we have gotten the backspace in "X\bX"; 52 * we expect one more 'X' which will put us back 53 * in LN_BOLDFACE). 54 */ 55 static int ln_state; /* Currently in normal/underline/bold/etc mode? */ 56 #define LN_NORMAL 0 /* Not in underline, boldface or whatever mode */ 57 #define LN_UNDERLINE 1 /* In underline, need next char */ 58 #define LN_UL_X 2 /* In underline, got char, need \b */ 59 #define LN_UL_XB 3 /* In underline, got char & \b, need one more */ 60 #define LN_BOLDFACE 4 /* In boldface, need next char */ 61 #define LN_BO_X 5 /* In boldface, got char, need \b */ 62 #define LN_BO_XB 6 /* In boldface, got char & \b, need same char */ 63 64 char *line; /* Pointer to the current line. 65 Usually points to linebuf. */ 66 67 extern int bs_mode; 68 extern int tabstop; 69 extern int bo_width, be_width; 70 extern int ul_width, ue_width; 71 extern int sc_width, sc_height; 72 73 /* 74 * Rewind the line buffer. 75 */ 76 prewind() 77 { 78 line = curr = linebuf; 79 ln_state = LN_NORMAL; 80 column = 0; 81 } 82 83 /* 84 * Append a character to the line buffer. 85 * Expand tabs into spaces, handle underlining, boldfacing, etc. 86 * Returns 0 if ok, 1 if couldn't fit in buffer. 87 */ 88 #define NEW_COLUMN(addon) \ 89 if (column + addon + (ln_state ? ue_width : 0) > sc_width) \ 90 return(1); \ 91 else \ 92 column += addon 93 94 pappend(c) 95 int c; 96 { 97 if (c == '\0') { 98 /* 99 * Terminate any special modes, if necessary. 100 * Append a '\0' to the end of the line. 101 */ 102 switch (ln_state) { 103 case LN_UL_X: 104 curr[0] = curr[-1]; 105 curr[-1] = UE_CHAR; 106 curr++; 107 break; 108 case LN_BO_X: 109 curr[0] = curr[-1]; 110 curr[-1] = BE_CHAR; 111 curr++; 112 break; 113 case LN_UL_XB: 114 case LN_UNDERLINE: 115 *curr++ = UE_CHAR; 116 break; 117 case LN_BO_XB: 118 case LN_BOLDFACE: 119 *curr++ = BE_CHAR; 120 break; 121 } 122 ln_state = LN_NORMAL; 123 *curr = '\0'; 124 return(0); 125 } 126 127 if (curr > linebuf + sizeof(linebuf) - 12) 128 /* 129 * Almost out of room in the line buffer. 130 * Don't take any chances. 131 * {{ Linebuf is supposed to be big enough that this 132 * will never happen, but may need to be made 133 * bigger for wide screens or lots of backspaces. }} 134 */ 135 return(1); 136 137 if (!bs_mode) { 138 /* 139 * Advance the state machine. 140 */ 141 switch (ln_state) { 142 case LN_NORMAL: 143 if (curr <= linebuf + 1 144 || curr[-1] != (char)('H' | 0200)) 145 break; 146 column -= 2; 147 if (c == curr[-2]) 148 goto enter_boldface; 149 if (c == '_' || curr[-2] == '_') 150 goto enter_underline; 151 curr -= 2; 152 break; 153 154 enter_boldface: 155 /* 156 * We have "X\bX" (including the current char). 157 * Switch into boldface mode. 158 */ 159 column--; 160 if (column + bo_width + be_width + 1 >= sc_width) 161 /* 162 * Not enough room left on the screen to 163 * enter and exit boldface mode. 164 */ 165 return (1); 166 167 if (bo_width > 0 && curr > linebuf + 2 168 && curr[-3] == ' ') { 169 /* 170 * Special case for magic cookie terminals: 171 * if the previous char was a space, replace 172 * it with the "enter boldface" sequence. 173 */ 174 curr[-3] = BO_CHAR; 175 column += bo_width-1; 176 } else { 177 curr[-1] = curr[-2]; 178 curr[-2] = BO_CHAR; 179 column += bo_width; 180 curr++; 181 } 182 goto ln_bo_xb_case; 183 184 enter_underline: 185 /* 186 * We have either "_\bX" or "X\b_" (including 187 * the current char). Switch into underline mode. 188 */ 189 column--; 190 if (column + ul_width + ue_width + 1 >= sc_width) 191 /* 192 * Not enough room left on the screen to 193 * enter and exit underline mode. 194 */ 195 return (1); 196 197 if (ul_width > 0 && 198 curr > linebuf + 2 && curr[-3] == ' ') 199 { 200 /* 201 * Special case for magic cookie terminals: 202 * if the previous char was a space, replace 203 * it with the "enter underline" sequence. 204 */ 205 curr[-3] = UL_CHAR; 206 column += ul_width-1; 207 } else 208 { 209 curr[-1] = curr[-2]; 210 curr[-2] = UL_CHAR; 211 column += ul_width; 212 curr++; 213 } 214 goto ln_ul_xb_case; 215 /*NOTREACHED*/ 216 case LN_UL_XB: 217 /* 218 * Termination of a sequence "_\bX" or "X\b_". 219 */ 220 if (c != '_' && curr[-2] != '_' && c == curr[-2]) 221 { 222 /* 223 * We seem to have run on from underlining 224 * into boldfacing - this is a nasty fix, but 225 * until this whole routine is rewritten as a 226 * real DFA, ... well ... 227 */ 228 curr[0] = curr[-2]; 229 curr[-2] = UE_CHAR; 230 curr[-1] = BO_CHAR; 231 curr += 2; /* char & non-existent backspace */ 232 ln_state = LN_BO_XB; 233 goto ln_bo_xb_case; 234 } 235 ln_ul_xb_case: 236 if (c == '_') 237 c = curr[-2]; 238 curr -= 2; 239 ln_state = LN_UNDERLINE; 240 break; 241 case LN_BO_XB: 242 /* 243 * Termination of a sequnce "X\bX". 244 */ 245 if (c != curr[-2] && (c == '_' || curr[-2] == '_')) 246 { 247 /* 248 * We seem to have run on from 249 * boldfacing into underlining. 250 */ 251 curr[0] = curr[-2]; 252 curr[-2] = BE_CHAR; 253 curr[-1] = UL_CHAR; 254 curr += 2; /* char & non-existent backspace */ 255 ln_state = LN_UL_XB; 256 goto ln_ul_xb_case; 257 } 258 ln_bo_xb_case: 259 curr -= 2; 260 ln_state = LN_BOLDFACE; 261 break; 262 case LN_UNDERLINE: 263 if (column + ue_width + bo_width + 1 + be_width >= sc_width) 264 /* 265 * We have just barely enough room to 266 * exit underline mode and handle a possible 267 * underline/boldface run on mixup. 268 */ 269 return (1); 270 ln_state = LN_UL_X; 271 break; 272 case LN_BOLDFACE: 273 if (c == '\b') 274 { 275 ln_state = LN_BO_XB; 276 break; 277 } 278 if (column + be_width + ul_width + 1 + ue_width >= sc_width) 279 /* 280 * We have just barely enough room to 281 * exit underline mode and handle a possible 282 * underline/boldface run on mixup. 283 */ 284 return (1); 285 ln_state = LN_BO_X; 286 break; 287 case LN_UL_X: 288 if (c == '\b') 289 ln_state = LN_UL_XB; 290 else 291 { 292 /* 293 * Exit underline mode. 294 * We have to shuffle the chars a bit 295 * to make this work. 296 */ 297 curr[0] = curr[-1]; 298 curr[-1] = UE_CHAR; 299 column += ue_width; 300 if (ue_width > 0 && curr[0] == ' ') 301 /* 302 * Another special case for magic 303 * cookie terminals: if the next 304 * char is a space, replace it 305 * with the "exit underline" sequence. 306 */ 307 column--; 308 else 309 curr++; 310 ln_state = LN_NORMAL; 311 } 312 break; 313 case LN_BO_X: 314 if (c == '\b') 315 ln_state = LN_BO_XB; 316 else 317 { 318 /* 319 * Exit boldface mode. 320 * We have to shuffle the chars a bit 321 * to make this work. 322 */ 323 curr[0] = curr[-1]; 324 curr[-1] = BE_CHAR; 325 column += be_width; 326 if (be_width > 0 && curr[0] == ' ') 327 /* 328 * Another special case for magic 329 * cookie terminals: if the next 330 * char is a space, replace it 331 * with the "exit boldface" sequence. 332 */ 333 column--; 334 else 335 curr++; 336 ln_state = LN_NORMAL; 337 } 338 break; 339 } 340 } 341 342 if (c == '\t') { 343 /* 344 * Expand a tab into spaces. 345 */ 346 do { 347 NEW_COLUMN(1); 348 } while ((column % tabstop) != 0); 349 *curr++ = '\t'; 350 return (0); 351 } 352 353 if (c == '\b') { 354 if (ln_state == LN_NORMAL) 355 NEW_COLUMN(2); 356 else 357 column--; 358 *curr++ = ('H' | 0200); 359 return(0); 360 } 361 362 if (CONTROL_CHAR(c)) { 363 /* 364 * Put a "^X" into the buffer. The 0200 bit is used to tell 365 * put_line() to prefix the char with a ^. We don't actually 366 * put the ^ in the buffer because we sometimes need to move 367 * chars around, and such movement might separate the ^ from 368 * its following character. 369 */ 370 NEW_COLUMN(2); 371 *curr++ = (CARAT_CHAR(c) | 0200); 372 return(0); 373 } 374 375 /* 376 * Ordinary character. Just put it in the buffer. 377 */ 378 NEW_COLUMN(1); 379 *curr++ = c; 380 return (0); 381 } 382 383 /* 384 * Analogous to forw_line(), but deals with "raw lines": 385 * lines which are not split for screen width. 386 * {{ This is supposed to be more efficient than forw_line(). }} 387 */ 388 off_t 389 forw_raw_line(curr_pos) 390 off_t curr_pos; 391 { 392 register char *p; 393 register int c; 394 off_t new_pos, ch_tell(); 395 396 if (curr_pos == NULL_POSITION || ch_seek(curr_pos) || 397 (c = ch_forw_get()) == EOI) 398 return (NULL_POSITION); 399 400 p = linebuf; 401 402 for (;;) 403 { 404 if (c == '\n' || c == EOI) 405 { 406 new_pos = ch_tell(); 407 break; 408 } 409 if (p >= &linebuf[sizeof(linebuf)-1]) 410 { 411 /* 412 * Overflowed the input buffer. 413 * Pretend the line ended here. 414 * {{ The line buffer is supposed to be big 415 * enough that this never happens. }} 416 */ 417 new_pos = ch_tell() - 1; 418 break; 419 } 420 *p++ = c; 421 c = ch_forw_get(); 422 } 423 *p = '\0'; 424 line = linebuf; 425 return (new_pos); 426 } 427 428 /* 429 * Analogous to back_line(), but deals with "raw lines". 430 * {{ This is supposed to be more efficient than back_line(). }} 431 */ 432 off_t 433 back_raw_line(curr_pos) 434 off_t curr_pos; 435 { 436 register char *p; 437 register int c; 438 off_t new_pos, ch_tell(); 439 440 if (curr_pos == NULL_POSITION || curr_pos <= (off_t)0 || 441 ch_seek(curr_pos-1)) 442 return (NULL_POSITION); 443 444 p = &linebuf[sizeof(linebuf)]; 445 *--p = '\0'; 446 447 for (;;) 448 { 449 c = ch_back_get(); 450 if (c == '\n') 451 { 452 /* 453 * This is the newline ending the previous line. 454 * We have hit the beginning of the line. 455 */ 456 new_pos = ch_tell() + 1; 457 break; 458 } 459 if (c == EOI) 460 { 461 /* 462 * We have hit the beginning of the file. 463 * This must be the first line in the file. 464 * This must, of course, be the beginning of the line. 465 */ 466 new_pos = (off_t)0; 467 break; 468 } 469 if (p <= linebuf) 470 { 471 /* 472 * Overflowed the input buffer. 473 * Pretend the line ended here. 474 */ 475 new_pos = ch_tell() + 1; 476 break; 477 } 478 *--p = c; 479 } 480 line = p; 481 return (new_pos); 482 } 483