xref: /netbsd-src/lib/libcurses/cr_put.c (revision 122b5006ee1bd67145794b4cde92f4fe4781a5ec)
1 /*	$NetBSD: cr_put.c,v 1.37 2021/09/06 07:45:48 rin Exp $	*/
2 
3 /*
4  * Copyright (c) 1981, 1993, 1994
5  *	The Regents of the University of California.  All rights reserved.
6  *
7  * Redistribution and use in source and binary forms, with or without
8  * modification, are permitted provided that the following conditions
9  * are met:
10  * 1. Redistributions of source code must retain the above copyright
11  *    notice, this list of conditions and the following disclaimer.
12  * 2. Redistributions in binary form must reproduce the above copyright
13  *    notice, this list of conditions and the following disclaimer in the
14  *    documentation and/or other materials provided with the distribution.
15  * 3. Neither the name of the University nor the names of its contributors
16  *    may be used to endorse or promote products derived from this software
17  *    without specific prior written permission.
18  *
19  * THIS SOFTWARE IS PROVIDED BY THE REGENTS AND CONTRIBUTORS ``AS IS'' AND
20  * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
21  * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
22  * ARE DISCLAIMED.  IN NO EVENT SHALL THE REGENTS OR CONTRIBUTORS BE LIABLE
23  * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
24  * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
25  * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
26  * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
27  * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
28  * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
29  * SUCH DAMAGE.
30  */
31 
32 #include <sys/cdefs.h>
33 #ifndef lint
34 #if 0
35 static char sccsid[] = "@(#)cr_put.c	8.3 (Berkeley) 5/4/94";
36 #else
37 __RCSID("$NetBSD: cr_put.c,v 1.37 2021/09/06 07:45:48 rin Exp $");
38 #endif
39 #endif				/* not lint */
40 
41 #include <string.h>
42 
43 #include "curses.h"
44 #include "curses_private.h"
45 
46 #define	HARDTABS	8
47 
48 /*
49  * Terminal driving and line formatting routines.  Basic motion optimizations
50  * are done here as well as formatting lines (printing of control characters,
51  * line numbering and the like).
52  */
53 
54 /* Stub function for the users. */
55 int
56 mvcur(int ly, int lx, int y, int x)
57 {
58 	return (__mvcur(ly, lx, y, x, 0));
59 }
60 
61 static void fgoto(int);
62 static int plod(int, int);
63 static int plodput(int);
64 static int tabcol(int, int);
65 
66 static int outcol, outline, destcol, destline;
67 
68 /*
69  * Sync the position of the output cursor.  Most work here is rounding for
70  * terminal boundaries getting the column position implied by wraparound or
71  * the lack thereof and rolling up the screen to get destline on the screen.
72  */
73 int
74 __mvcur(int ly, int lx, int y, int x, int in_refresh)
75 {
76 	__CTRACE(__CTRACE_OUTPUT,
77 	    "mvcur: moving cursor from (%d, %d) to (%d, %d) in refresh %d\n",
78 	    ly, lx, y, x, in_refresh);
79 	destcol = x;
80 	destline = y;
81 	outcol = lx;
82 	outline = ly;
83 	fgoto(in_refresh);
84 	return (OK);
85 }
86 
87 static void
88 fgoto(int in_refresh)
89 {
90 	int	 c, l;
91 	char	*cgp;
92 
93 	__CTRACE(__CTRACE_OUTPUT, "fgoto: in_refresh=%d\n", in_refresh);
94 	__CTRACE(__CTRACE_OUTPUT,
95 	    "fgoto: outcol=%d, outline=%d, destcol=%d, destline=%d\n",
96 	    outcol, outline, destcol, destline);
97 	if (destcol >= COLS) {
98 		destline += destcol / COLS;
99 		destcol %= COLS;
100 	}
101 	if (outcol >= COLS) {
102 		l = (outcol + 1) / COLS;
103 		outline += l;
104 		outcol %= COLS;
105 		if (auto_left_margin == 0) {
106 			while (l > 0) {
107 				if (__pfast) {
108 					if (carriage_return)
109 						tputs(carriage_return,
110 							0, __cputchar);
111 					else
112 						__cputchar('\r');
113 				}
114 				if (cursor_down)
115 					tputs(cursor_down, 0, __cputchar);
116 				else
117 					__cputchar('\n');
118 				l--;
119 			}
120 			outcol = 0;
121 		}
122 		if (outline > LINES - 1) {
123 			destline -= outline - (LINES - 1);
124 			outline = LINES - 1;
125 		}
126 	}
127 	if (destline >= LINES) {
128 		l = destline;
129 		destline = LINES - 1;
130 		if (outline < LINES - 1) {
131 			c = destcol;
132 			if (__pfast == 0 && !cursor_address)
133 				destcol = 0;
134 			fgoto(in_refresh);
135 			destcol = c;
136 		}
137 		while (l >= LINES) {
138 			/* The following linefeed (or simulation thereof) is
139 			 * supposed to scroll up the screen, since we are on
140 			 * the bottom line.  We make the assumption that
141 			 * linefeed will scroll.  If ns is in the capability
142 			 * list this won't work.  We should probably have an
143 			 * sc capability but sf will generally take the place
144 			 * if it works.
145 			 *
146 			 * Superbee glitch: in the middle of the screen have to
147 			 * use esc B (down) because linefeed screws up in
148 			 * "Efficient Paging" (what a joke) mode (which is
149 			 * essential in some SB's because CRLF mode puts
150 			 * garbage in at end of memory), but you must use
151 			 * linefeed to scroll since down arrow won't go past
152 			 * memory end. I turned this off after recieving Paul
153 			 * Eggert's Superbee description which wins better. */
154 			if (cursor_down /* && !__tc_xb */ && __pfast)
155 				tputs(cursor_down, 0, __cputchar);
156 			else
157 				__cputchar('\n');
158 			l--;
159 			if (__pfast == 0)
160 				outcol = 0;
161 		}
162 	}
163 	if (destline < outline && !(cursor_address || cursor_up))
164 		destline = outline;
165 
166 	if (cursor_address &&
167 	    (cgp = tiparm(cursor_address, destline, destcol)))
168 	{
169 		/*
170 		 * Need this condition due to inconsistent behavior
171 		 * of backspace on the last column.
172 		 */
173 		__CTRACE(__CTRACE_OUTPUT, "fgoto: cgp=%s\n", cgp);
174 		if (outcol != COLS - 1 &&
175 		    plod((int) strlen(cgp), in_refresh) > 0)
176 			plod(0, in_refresh);
177 		else
178 			tputs(cgp, 0, __cputchar);
179 	} else
180 		plod(0, in_refresh);
181 	outline = destline;
182 	outcol = destcol;
183 }
184 
185 /*
186  * Move (slowly) to destination.
187  * Hard thing here is using home cursor on really deficient terminals.
188  * Otherwise just use cursor motions, hacking use of tabs and overtabbing
189  * and backspace.
190  *
191  * XXX this needs to be revisited for wide characters since we may output
192  * XXX more than one byte for a character.
193  */
194 
195 static int plodcnt, plodflg;
196 
197 static int
198 plodput(int c)
199 {
200 	if (plodflg)
201 		--plodcnt;
202 	else
203 		__cputchar(c);
204 	return 0;
205 }
206 
207 static int
208 plod(int cnt, int in_refresh)
209 {
210 	int	 i, j, k, soutcol, soutline;
211 
212 	__CTRACE(__CTRACE_OUTPUT, "plod: cnt=%d, in_refresh=%d\n",
213 	    cnt, in_refresh);
214 	__CTRACE(__CTRACE_OUTPUT,
215 	    "plod: plodding from col %d, row %d to col %d, row %d\n",
216 	    outcol, outline, destcol, destline);
217 	plodcnt = plodflg = cnt;
218 	soutcol = outcol;
219 	soutline = outline;
220 
221 	/*
222 	 * Consider homing and moving down/right from there, vs. moving
223 	 * directly with local motions to the right spot.
224 	 */
225 	if (cursor_home) {
226 		/*
227 		 * i is the cost to home and tab/space to the right to get to
228 		 * the proper column.  This assumes nd space costs 1 char.  So
229 		 * i + destcol is cost of motion with home.
230 		 */
231 		if (__GT)
232 			i = (destcol / HARDTABS) + (destcol % HARDTABS);
233 		else
234 			i = destcol;
235 
236 		/* j is cost to move locally without homing. */
237 		if (destcol >= outcol) {	/* if motion is to the right */
238 			j = destcol / HARDTABS - outcol / HARDTABS;
239 			if (__GT && j)
240 				j += destcol % HARDTABS;
241 			else
242 				j = destcol - outcol;
243 		} else
244 			/* leftward motion only works if we can backspace. */
245 			if (outcol - destcol <= i)
246 				/* Cheaper to backspace. */
247 				i = j = outcol - destcol;
248 			else
249 				/* Impossibly expensive. */
250 				j = i + 1;
251 
252 		/* k is the absolute value of vertical distance. */
253 		k = outline - destline;
254 		if (k < 0)
255 			k = -k;
256 		j += k;
257 
258 		/* Decision.  We may not have a choice if no up. */
259 		if (i + destline < j || (!cursor_up && destline < outline)) {
260 			/*
261 			 * Cheaper to home.  Do it now and pretend it's a
262 			 * regular local motion.
263 			 */
264 			tputs(cursor_home, 0, plodput);
265 			outcol = outline = 0;
266 		} else
267 			if (cursor_to_ll) {
268 				/*
269 				 * Quickly consider homing down and moving from
270 				 * there.  Assume cost of ll is 2.
271 				 */
272 				k = (LINES - 1) - destline;
273 				if (i + k + 2 < j && (k <= 0 || cursor_up)) {
274 					tputs(cursor_to_ll, 0, plodput);
275 					outcol = 0;
276 					outline = LINES - 1;
277 				}
278 			}
279 	} else
280 		/* No home and no up means it's impossible. */
281 		if (!cursor_up && destline < outline)
282 			return (-1);
283 	if (__GT)
284 		i = destcol % HARDTABS + destcol / HARDTABS;
285 	else
286 		i = destcol;
287 #ifdef notdef
288 	if (back_tab && outcol > destcol &&
289 	    (j = (((outcol + 7) & ~7) - destcol - 1) >> 3)) {
290 		j *= (k = strlen(back_tab));
291 		if ((k += (destcol & 7)) > 4)
292 			j += 8 - (destcol & 7);
293 		else
294 			j += k;
295 	} else
296 #endif
297 		j = outcol - destcol;
298 
299 	/*
300 	 * If we will later need a \n which will turn into a \r\n by the
301 	 * system or the terminal, then don't bother to try to \r.
302 	 */
303 	if ((__NONL || !__pfast) && outline < destline)
304 		goto dontcr;
305 
306 	/*
307 	 * If the terminal will do a \r\n and there isn't room for it, then
308 	 * we can't afford a \r.
309 	 */
310 	if (!carriage_return && outline >= destline)
311 		goto dontcr;
312 
313 	/*
314 	 * If it will be cheaper, or if we can't back up, then send a return
315 	 * preliminarily.
316 	 */
317 	if (j > i + 1 || outcol > destcol) {
318 		/*
319 		 * BUG: this doesn't take the (possibly long) length of cr
320 		 * into account.
321 		 */
322 		if (carriage_return)
323 			tputs(carriage_return, 0, plodput);
324 		else
325 			plodput('\r');
326 		if (!carriage_return) {
327 			if (cursor_down)
328 				tputs(cursor_down, 0, plodput);
329 			else
330 				plodput('\n');
331 			outline++;
332 		}
333 
334 		outcol = 0;
335 	}
336 dontcr:while (outline < destline) {
337 		outline++;
338 		if (cursor_down)
339 			tputs(cursor_down, 0, plodput);
340 		else
341 			plodput('\n');
342 		if (plodcnt < 0)
343 			goto out;
344 		/*
345 		 * If the terminal does a CR with NL or we are in
346 		 * a mode where a \n will result in an implicit \r
347 		 * then adjust the outcol to match iff we actually
348 		 * emitted said \n.
349 		 */
350 		if ((__NONL || __pfast == 0) &&
351 		    (!cursor_down || (*cursor_down == '\n')))
352 			outcol = 0;
353 	}
354 #ifdef notdef
355 	if (back_tab)
356 		k = (int) strlen(back_tab);
357 #endif
358 	while (outcol > destcol) {
359 		if (plodcnt < 0)
360 			goto out;
361 #ifdef notdef
362 		if (back_tab && outcol - destcol > k + 4) {
363 			tputs(back_tab, 0, plodput);
364 			outcol--;
365 			outcol &= ~7;
366 			continue;
367 		}
368 #endif
369 		outcol--;
370 		if (cursor_left)
371 			tputs(cursor_left, 0, plodput);
372 		else
373 			plodput('\b');
374 	}
375 	while (outline > destline) {
376 		outline--;
377 		tputs(cursor_up, 0, plodput);
378 		if (plodcnt < 0)
379 			goto out;
380 	}
381 	if (__GT && destcol - outcol > 1) {
382 		for (;;) {
383 			i = tabcol(outcol, HARDTABS);
384 			if (i > destcol)
385 				break;
386 			if (tab)
387 				tputs(tab, 0, plodput);
388 			else
389 				plodput('\t');
390 			outcol = i;
391 		}
392 		if (destcol - outcol > 4 && i < COLS) {
393 			if (tab)
394 				tputs(tab, 0, plodput);
395 			else
396 				plodput('\t');
397 			outcol = i;
398 			while (outcol > destcol) {
399 				outcol--;
400 				if (cursor_left)
401 					tputs(cursor_left, 0, plodput);
402 				else
403 					plodput('\b');
404 			}
405 		}
406 	}
407 	while (outcol < destcol) {
408 		/*
409 		 * Move one char to the right.  We don't use nd space because
410 		 * it's better to just print the char we are moving over.
411 		 */
412 		if (in_refresh)
413 			if (plodflg)	/* Avoid a complex calculation. */
414 				plodcnt--;
415 			else {
416 #ifndef HAVE_WCHAR
417 				i = curscr->alines[outline]->line[outcol].ch
418 				    & __CHARTEXT;
419 				if (curscr->alines[outline]->line[outcol].attr
420 				    == curscr->wattr)
421 					__cputchar(i);
422 #else
423 				if ((curscr->alines[outline]->line[outcol].attr
424 				    & WA_ATTRIBUTES)
425 				    == curscr->wattr) {
426 					switch (WCOL(curscr->alines[outline]->line[outcol])) {
427 					case 1:
428 						__cputwchar(curscr->alines[outline]->line[outcol].ch);
429 						__cursesi_putnsp(curscr->alines[outline]->line[outcol].nsp,
430 								outline,
431 								outcol);
432 						__CTRACE(__CTRACE_OUTPUT,
433 						    "plod: (%d,%d)WCOL(%d), "
434 						    "putwchar(%x)\n",
435 						    outline, outcol,
436 						    WCOL(curscr->alines[outline]->line[outcol]),
437 						    curscr->alines[outline]->line[outcol].ch);
438 					/*FALLTHROUGH*/
439 					case 0:
440 						break;
441 					default:
442 						goto nondes;
443 					}
444 				}
445 #endif /* HAVE_WCHAR */
446 				else
447 					goto nondes;
448 			}
449 		else
450 	nondes:	if (cursor_right)
451 			tputs(cursor_right, 0, plodput);
452 		else
453 			plodput(' ');
454 		outcol++;
455 		if (plodcnt < 0)
456 			goto out;
457 	}
458 
459 out:	if (plodflg) {
460 		outcol = soutcol;
461 		outline = soutline;
462 	}
463 	__CTRACE(__CTRACE_OUTPUT, "plod: returns %d\n", plodcnt);
464 	return plodcnt;
465 }
466 
467 /*
468  * Return the column number that results from being in column col and
469  * hitting a tab, where tabs are set every ts columns.  Work right for
470  * the case where col > COLS, even if ts does not divide COLS.
471  */
472 static int
473 tabcol(int col, int ts)
474 {
475 	int	 offset;
476 
477 	if (col >= COLS) {
478 		offset = COLS * (col / COLS);
479 		col -= offset;
480 	} else
481 		offset = 0;
482 	return (col + ts - (col % ts) + offset);
483 }
484