xref: /openbsd/usr.bin/mg/tty.c (revision 8529ddd3)
1 /*	$OpenBSD: tty.c,v 1.35 2015/03/19 21:48:05 bcallah 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 	/* Case of one line insert is special. */
244 	if (row == bot) {
245 		ttmove(row, 0);
246 		tteeol();
247 		return;
248 	}
249 	if (change_scroll_region && scroll_reverse) {
250 		/* Use scroll region and back index	 */
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 if (insdel) {
259 		ttmove(1 + bot - nchunk, 0);
260 		nl = nrow - ttrow;
261 		if (parm_delete_line)
262 			putpad(tgoto(parm_delete_line, 0, nchunk), nl);
263 		else
264 			/* For all lines in the chunk... */
265 			for (i = 0; i < nchunk; i++)
266 				putpad(delete_line, nl);
267 		ttmove(row, 0);
268 
269 		/* ttmove() changes ttrow */
270 		nl = nrow - ttrow;
271 
272 		if (parm_insert_line)
273 			putpad(tgoto(parm_insert_line, 0, nchunk), nl);
274 		else
275 			/* For all lines in the chunk */
276 			for (i = 0; i < nchunk; i++)
277 				putpad(insert_line, nl);
278 		ttrow = HUGE;
279 		ttcol = HUGE;
280 	} else
281 		panic("ttinsl: Can't insert/delete line");
282 }
283 
284 /*
285  * Delete nchunk line(s) from "row", replacing the bottom line on the
286  * screen with a blank line.  Unless we're using the scrolling region,
287  * this is done with crafty sequences of insert and delete lines.  The
288  * presence of the echo area makes a boundary condition go away.
289  */
290 void
291 ttdell(int row, int bot, int nchunk)
292 {
293 	int	i, nl;
294 
295 	/* One line special cases */
296 	if (row == bot) {
297 		ttmove(row, 0);
298 		tteeol();
299 		return;
300 	}
301 	/* scrolling region */
302 	if (change_scroll_region) {
303 		nl = bot - row;
304 		ttwindow(row, bot);
305 		ttmove(bot, 0);
306 		while (nchunk--)
307 			putpad(scroll_fwd, nl);
308 		ttnowindow();
309 	/* else use insert/delete line */
310 	} else if (insdel) {
311 		ttmove(row, 0);
312 		nl = nrow - ttrow;
313 		if (parm_delete_line)
314 			putpad(tgoto(parm_delete_line, 0, nchunk), nl);
315 		else
316 			/* For all lines in the chunk	 */
317 			for (i = 0; i < nchunk; i++)
318 				putpad(delete_line, nl);
319 		ttmove(1 + bot - nchunk, 0);
320 
321 		/* ttmove() changes ttrow */
322 		nl = nrow - ttrow;
323 		if (parm_insert_line)
324 			putpad(tgoto(parm_insert_line, 0, nchunk), nl);
325 		else
326 			/* For all lines in the chunk */
327 			for (i = 0; i < nchunk; i++)
328 				putpad(insert_line, nl);
329 		ttrow = HUGE;
330 		ttcol = HUGE;
331 	} else
332 		panic("ttdell: Can't insert/delete line");
333 }
334 
335 /*
336  * This routine sets the scrolling window on the display to go from line
337  * "top" to line "bot" (origin 0, inclusive).  The caller checks for the
338  * pathological 1-line scroll window which doesn't work right and avoids
339  * it.  The "ttrow" and "ttcol" variables are set to a crazy value to
340  * ensure that the next call to "ttmove" does not turn into a no-op (the
341  * window adjustment moves the cursor).
342  */
343 void
344 ttwindow(int top, int bot)
345 {
346 	if (change_scroll_region && (tttop != top || ttbot != bot)) {
347 		putpad(tgoto(change_scroll_region, bot, top), nrow - ttrow);
348 		ttrow = HUGE;	/* Unknown.		 */
349 		ttcol = HUGE;
350 		tttop = top;	/* Remember region.	 */
351 		ttbot = bot;
352 	}
353 }
354 
355 /*
356  * Switch to full screen scroll. This is used by "spawn.c" just before it
357  * suspends the editor and by "display.c" when it is getting ready to
358  * exit.  This function does a full screen scroll by telling the terminal
359  * to set a scrolling region that is lines or nrow rows high, whichever is
360  * larger.  This behavior seems to work right on systems where you can set
361  * your terminal size.
362  */
363 void
364 ttnowindow(void)
365 {
366 	if (change_scroll_region) {
367 		putpad(tgoto(change_scroll_region,
368 		    (nrow > lines ? nrow : lines) - 1, 0), nrow - ttrow);
369 		ttrow = HUGE;	/* Unknown.		 */
370 		ttcol = HUGE;
371 		tttop = HUGE;	/* No scroll region.	 */
372 		ttbot = HUGE;
373 	}
374 }
375 
376 /*
377  * Set the current writing color to the specified color. Watch for color
378  * changes that are not going to do anything (the color is already right)
379  * and don't send anything to the display.  The rainbow version does this
380  * in putline.s on a line by line basis, so don't bother sending out the
381  * color shift.
382  */
383 void
384 ttcolor(int color)
385 {
386 	if (color != tthue) {
387 		if (color == CTEXT)
388 			/* normal video */
389 			putpad(exit_standout_mode, 1);
390 		else if (color == CMODE)
391 			/* reverse video */
392 			putpad(enter_standout_mode, 1);
393 		/* save the color */
394 		tthue = color;
395 	}
396 }
397 
398 /*
399  * This routine is called by the "refresh the screen" command to try
400  * to resize the display. Look in "window.c" to see how
401  * the caller deals with a change.
402  *
403  * We use `newrow' and `newcol' so vtresize() know the difference between the
404  * new and old settings.
405  */
406 void
407 ttresize(void)
408 {
409 	int newrow = 0, newcol = 0;
410 
411 	struct	winsize winsize;
412 
413 	if (ioctl(0, TIOCGWINSZ, &winsize) == 0) {
414 		newrow = winsize.ws_row;
415 		newcol = winsize.ws_col;
416 	}
417 	if ((newrow <= 0 || newcol <= 0) &&
418 	    ((newrow = lines) <= 0 || (newcol = columns) <= 0)) {
419 		newrow = 24;
420 		newcol = 80;
421 	}
422 	if (vtresize(1, newrow, newcol) != TRUE)
423 		panic("vtresize failed");
424 }
425 
426 /*
427  * fake char output for charcost()
428  */
429 /* ARGSUSED */
430 static int
431 fakec(int c)
432 {
433 	cci++;
434 	return (0);
435 }
436 
437 /* calculate the cost of doing string s */
438 static int
439 charcost(const char *s)
440 {
441 	cci = 0;
442 
443 	tputs(s, nrow, fakec);
444 	return (cci);
445 }
446