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