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