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