xref: /openbsd-src/usr.bin/mg/tty.c (revision b2ea75c1b17e1a9a339660e7ed45cd24946b230e)
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