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