1 /* $OpenBSD: tty.c,v 1.9 2001/05/24 03:05:26 mickey Exp $ */ 2 3 /* 4 * Terminfo display driver 5 * 6 * Terminfo is a terminal information database and routines to describe 7 * terminals on most modern UNIX systems. Many other systems have adopted 8 * this as a reasonable way to allow for widly varying and ever changing 9 * varieties of terminal types. This should be used where practical. 10 */ 11 /* 12 * Known problems: If you have a terminal with no clear to end of screen and 13 * memory of lines below the ones visible on the screen, display will be 14 * wrong in some cases. I doubt that any such terminal was ever made, but I 15 * thought everyone with delete line would have clear to end of screen too... 16 * 17 * Code for terminals without clear to end of screen and/or clear to end of line 18 * has not been extensivly tested. 19 * 20 * Cost calculations are very rough. Costs of insert/delete line may be far 21 * from the truth. This is accentuated by display.c not knowing about 22 * multi-line insert/delete. 23 * 24 * Using scrolling region vs insert/delete line should probably be based on cost 25 * rather than the assuption that scrolling region operations look better. 26 */ 27 28 #include "def.h" 29 30 #include <term.h> 31 32 #ifdef NO_RESIZE 33 static int setttysize __P((void)); 34 #endif /* NO_RESIZE */ 35 36 static int charcost __P((char *)); 37 38 static int cci; 39 static int insdel; /* Do we have both insert & delete line? */ 40 static char *scroll_fwd; /* How to scroll forward. */ 41 42 /* 43 * Initialize the terminal when the editor 44 * gets started up. 45 */ 46 void 47 ttinit() 48 { 49 char *tv_stype, *p; 50 51 /* system dependent function to determine terminal type, if necessary. */ 52 #ifndef gettermtype 53 char *gettermtype(); 54 #endif /* gettermtype */ 55 56 if ((tv_stype = gettermtype()) == NULL) 57 panic("Could not determine terminal type!"); 58 59 if (setupterm(tv_stype, 1, NULL)) { 60 (void)asprintf(&p, "Unknown terminal type: %s", tv_stype); 61 panic(p); 62 } 63 64 scroll_fwd = scroll_forward; 65 if (scroll_fwd == NULL || *scroll_fwd == '\0') { 66 /* this is what GNU Emacs does */ 67 scroll_fwd = parm_down_cursor; 68 if (scroll_fwd == NULL || *scroll_fwd == '\0') 69 scroll_fwd = "\n"; 70 } 71 72 if (cursor_address == NULL || cursor_up == NULL) 73 panic("This terminal is too stupid to run mg"); 74 75 /* set nrow & ncol */ 76 ttresize(); 77 78 if (!clr_eol) 79 tceeol = ncol; 80 else 81 tceeol = charcost(clr_eol); 82 83 /* Estimate cost of inserting a line */ 84 if (change_scroll_region && scroll_reverse) 85 tcinsl = charcost(change_scroll_region) * 2 + 86 charcost(scroll_reverse); 87 else if (parm_insert_line) 88 tcinsl = charcost(parm_insert_line); 89 else if (insert_line) 90 tcinsl = charcost(insert_line); 91 else 92 /* make this cost high enough */ 93 tcinsl = NROW * NCOL; 94 95 /* Estimate cost of deleting a line */ 96 if (change_scroll_region) 97 tcdell = charcost(change_scroll_region) * 2 + 98 charcost(scroll_fwd); 99 else if (parm_delete_line) 100 tcdell = charcost(parm_delete_line); 101 else if (delete_line) 102 tcdell = charcost(delete_line); 103 else 104 /* make this cost high enough */ 105 tcdell = NROW * NCOL; 106 107 /* Flag to indicate that we can both insert and delete lines */ 108 insdel = (insert_line || parm_insert_line) && 109 (delete_line || parm_delete_line); 110 111 if (enter_ca_mode) 112 /* enter application mode */ 113 putpad(enter_ca_mode, 1); 114 115 setttysize(); 116 } 117 118 /* 119 * Re-initialize the terminal when the editor is resumed. 120 * The keypad_xmit doesn't really belong here but... 121 */ 122 void 123 ttreinit() 124 { 125 if (enter_ca_mode) 126 /* enter application mode */ 127 putpad(enter_ca_mode, 1); 128 if (keypad_xmit) 129 /* turn on keypad */ 130 putpad(keypad_xmit, 1); 131 132 setttysize(); 133 } 134 135 /* 136 * Clean up the terminal, in anticipation of a return to the command 137 * interpreter. This is a no-op on the ANSI display. On the SCALD display, 138 * it sets the window back to half screen scrolling. Perhaps it should 139 * query the display for the increment, and put it back to what it was. 140 */ 141 void 142 tttidy() 143 { 144 #ifdef XKEYS 145 ttykeymaptidy(); 146 #endif /* XKEYS */ 147 148 /* set the term back to normal mode */ 149 if (exit_ca_mode) 150 putpad(exit_ca_mode, 1); 151 } 152 153 /* 154 * Move the cursor to the specified origin 0 row and column position. Try to 155 * optimize out extra moves; redisplay may have left the cursor in the right 156 * location last time! 157 */ 158 void 159 ttmove(row, col) 160 int row, col; 161 { 162 if (ttrow != row || ttcol != col) { 163 putpad(tgoto(cursor_address, col, row), 1); 164 ttrow = row; 165 ttcol = col; 166 } 167 } 168 169 /* 170 * Erase to end of line. 171 */ 172 void 173 tteeol() 174 { 175 int i; 176 177 if (clr_eol) 178 putpad(clr_eol, 1); 179 else { 180 i = ncol - ttcol; 181 while (i--) 182 ttputc(' '); 183 ttrow = ttcol = HUGE; 184 } 185 } 186 187 /* 188 * Erase to end of page. 189 */ 190 void 191 tteeop() 192 { 193 int line; 194 195 if (clr_eos) 196 putpad(clr_eos, nrow - ttrow); 197 else { 198 putpad(clr_eol, 1); 199 if (insdel) 200 ttdell(ttrow + 1, lines, lines - ttrow - 1); 201 else { 202 /* do it by hand */ 203 for (line = ttrow + 1; line <= lines; ++line) { 204 ttmove(line, 0); 205 tteeol(); 206 } 207 } 208 ttrow = ttcol = HUGE; 209 } 210 } 211 212 /* 213 * Make a noise. 214 */ 215 void 216 ttbeep() 217 { 218 putpad(bell, 1); 219 ttflush(); 220 } 221 222 /* 223 * Insert nchunk blank line(s) onto the screen, scrolling the last line on 224 * the screen off the bottom. Use the scrolling region if possible for a 225 * smoother display. If there is no scrolling region, use a set of insert 226 * and delete line sequences. 227 */ 228 void 229 ttinsl(row, bot, nchunk) 230 int row, bot, nchunk; 231 { 232 int i, nl; 233 234 /* Case of one line insert is special. */ 235 if (row == bot) { 236 ttmove(row, 0); 237 tteeol(); 238 return; 239 } 240 if (change_scroll_region && scroll_reverse) { 241 /* Use scroll region and back index */ 242 nl = bot - row; 243 ttwindow(row, bot); 244 ttmove(row, 0); 245 while (nchunk--) 246 putpad(scroll_reverse, nl); 247 ttnowindow(); 248 return; 249 } else if (insdel) { 250 ttmove(1 + bot - nchunk, 0); 251 nl = nrow - ttrow; 252 if (parm_delete_line) 253 putpad(tgoto(parm_delete_line, 0, nchunk), nl); 254 else 255 /* For all lines in the chunk... */ 256 for (i = 0; i < nchunk; i++) 257 putpad(delete_line, nl); 258 ttmove(row, 0); 259 260 /* ttmove() changes ttrow */ 261 nl = nrow - ttrow; 262 263 if (parm_insert_line) 264 putpad(tgoto(parm_insert_line, 0, nchunk), nl); 265 else 266 /* For all lines in the chunk */ 267 for (i = 0; i < nchunk; i++) 268 putpad(insert_line, nl); 269 ttrow = HUGE; 270 ttcol = HUGE; 271 } else 272 panic("ttinsl: Can't insert/delete line"); 273 } 274 275 /* 276 * Delete nchunk line(s) from "row", replacing the bottom line on the 277 * screen with a blank line. Unless we're using the scrolling region, 278 * this is done with crafty sequences of insert and delete lines. The 279 * presence of the echo area makes a boundry condition go away. 280 */ 281 void 282 ttdell(row, bot, nchunk) 283 int row, bot, nchunk; 284 { 285 int i, nl; 286 287 /* One line special cases */ 288 if (row == bot) { 289 ttmove(row, 0); 290 tteeol(); 291 return; 292 } 293 /* scrolling region */ 294 if (change_scroll_region) { 295 nl = bot - row; 296 ttwindow(row, bot); 297 ttmove(bot, 0); 298 while (nchunk--) 299 putpad(scroll_fwd, nl); 300 ttnowindow(); 301 /* else use insert/delete line */ 302 } else if (insdel) { 303 ttmove(row, 0); 304 nl = nrow - ttrow; 305 if (parm_delete_line) 306 putpad(tgoto(parm_delete_line, 0, nchunk), nl); 307 else 308 /* For all lines in the chunk */ 309 for (i = 0; i < nchunk; i++) 310 putpad(delete_line, nl); 311 ttmove(1 + bot - nchunk, 0); 312 313 /* ttmove() changes ttrow */ 314 nl = nrow - ttrow; 315 if (parm_insert_line) 316 putpad(tgoto(parm_insert_line, 0, nchunk), nl); 317 else 318 /* For all lines in the chunk */ 319 for (i = 0; i < nchunk; i++) 320 putpad(insert_line, nl); 321 ttrow = HUGE; 322 ttcol = HUGE; 323 } else 324 panic("ttdell: Can't insert/delete line"); 325 } 326 327 /* 328 * This routine sets the scrolling window on the display to go from line 329 * "top" to line "bot" (origin 0, inclusive). The caller checks for the 330 * pathological 1-line scroll window which doesn't work right and avoids 331 * it. The "ttrow" and "ttcol" variables are set to a crazy value to 332 * ensure that the next call to "ttmove" does not turn into a no-op (the 333 * window adjustment moves the cursor). 334 */ 335 void 336 ttwindow(top, bot) 337 int top, bot; 338 { 339 if (change_scroll_region && (tttop != top || ttbot != bot)) { 340 putpad(tgoto(change_scroll_region, bot, top), nrow - ttrow); 341 ttrow = HUGE; /* Unknown. */ 342 ttcol = HUGE; 343 tttop = top; /* Remember region. */ 344 ttbot = bot; 345 } 346 } 347 348 /* 349 * Switch to full screen scroll. This is used by "spawn.c" just before it 350 * suspends the editor and by "display.c" when it is getting ready to 351 * exit. This function does a full screen scroll by telling the terminal 352 * to set a scrolling region that is lines or nrow rows high, whichever is 353 * larger. This behavior seems to work right on systems where you can set 354 * your terminal size. 355 */ 356 void 357 ttnowindow() 358 { 359 if (change_scroll_region) { 360 putpad(tgoto(change_scroll_region, 361 (nrow > lines ? nrow : lines) - 1, 0), nrow - ttrow); 362 ttrow = HUGE; /* Unknown. */ 363 ttcol = HUGE; 364 tttop = HUGE; /* No scroll region. */ 365 ttbot = HUGE; 366 } 367 } 368 369 /* 370 * Set the current writing color to the specified color. Watch for color 371 * changes that are not going to do anything (the color is already right) 372 * and don't send anything to the display. The rainbow version does this 373 * in putline.s on a line by line basis, so don't bother sending out the 374 * color shift. 375 */ 376 void 377 ttcolor(color) 378 int color; 379 { 380 if (color != tthue) { 381 if (color == CTEXT) 382 /* normal video */ 383 putpad(exit_standout_mode, 1); 384 else if (color == CMODE) 385 /* reverse video */ 386 putpad(enter_standout_mode, 1); 387 /* save the color */ 388 tthue = color; 389 } 390 } 391 392 /* 393 * This routine is called by the "refresh the screen" command to try 394 * to resize the display. The new size, which must not exceed the NROW 395 * and NCOL limits, is stored back into "nrow" and * "ncol". Display can 396 * always deal with a screen NROW by NCOL. Look in "window.c" to see how 397 * the caller deals with a change. 398 */ 399 void 400 ttresize() 401 { 402 /* found in "ttyio.c" */ 403 setttysize(); 404 405 /* ask OS for tty size and check limits */ 406 if (nrow < 1) 407 nrow = 1; 408 else if (nrow > NROW) 409 nrow = NROW; 410 if (ncol < 1) 411 ncol = 1; 412 else if (ncol > NCOL) 413 ncol = NCOL; 414 } 415 416 #ifdef NO_RESIZE 417 static 418 setttysize() 419 { 420 nrow = lines; 421 ncol = columns; 422 } 423 #endif /* NO_RESIZE */ 424 425 /* 426 * fake char output for charcost() 427 */ 428 /* ARGSUSED */ 429 static int 430 fakec(c) 431 char c; 432 { 433 cci++; 434 return 0; 435 } 436 437 /* calculate the cost of doing string s */ 438 static int 439 charcost(s) 440 char *s; 441 { 442 int cci = 0; 443 444 tputs(s, nrow, fakec); 445 return (cci); 446 } 447