xref: /netbsd-src/lib/libcurses/cr_put.c (revision e670fd5c413e99c2f6a37901bb21c537fcd322d2)
1 /*	$NetBSD: cr_put.c,v 1.35 2021/06/27 23:57:08 blymn 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.35 2021/06/27 23:57:08 blymn 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 #ifdef DEBUG
77 	__CTRACE(__CTRACE_OUTPUT,
78 	    "mvcur: moving cursor from (%d, %d) to (%d, %d) in refresh %d\n", ly, lx, y, x, in_refresh);
79 #endif
80 	destcol = x;
81 	destline = y;
82 	outcol = lx;
83 	outline = ly;
84 	fgoto(in_refresh);
85 	return (OK);
86 }
87 
88 static void
89 fgoto(int in_refresh)
90 {
91 	int	 c, l;
92 	char	*cgp;
93 
94 #ifdef DEBUG
95 	__CTRACE(__CTRACE_OUTPUT, "fgoto: in_refresh=%d\n", in_refresh);
96 	__CTRACE(__CTRACE_OUTPUT, "fgoto: outcol=%d, outline=%d, destcol=%d, destline=%d\n",
97 		outcol, outline, destcol, destline);
98 #endif /* DEBUG */
99 	if (destcol >= COLS) {
100 		destline += destcol / COLS;
101 		destcol %= COLS;
102 	}
103 	if (outcol >= COLS) {
104 		l = (outcol + 1) / COLS;
105 		outline += l;
106 		outcol %= COLS;
107 		if (auto_left_margin == 0) {
108 			while (l > 0) {
109 				if (__pfast) {
110 					if (carriage_return)
111 						tputs(carriage_return,
112 							0, __cputchar);
113 					else
114 						__cputchar('\r');
115 				}
116 				if (cursor_down)
117 					tputs(cursor_down, 0, __cputchar);
118 				else
119 					__cputchar('\n');
120 				l--;
121 			}
122 			outcol = 0;
123 		}
124 		if (outline > LINES - 1) {
125 			destline -= outline - (LINES - 1);
126 			outline = LINES - 1;
127 		}
128 	}
129 	if (destline >= LINES) {
130 		l = destline;
131 		destline = LINES - 1;
132 		if (outline < LINES - 1) {
133 			c = destcol;
134 			if (__pfast == 0 && !cursor_address)
135 				destcol = 0;
136 			fgoto(in_refresh);
137 			destcol = c;
138 		}
139 		while (l >= LINES) {
140 			/* The following linefeed (or simulation thereof) is
141 			 * supposed to scroll up the screen, since we are on
142 			 * the bottom line.  We make the assumption that
143 			 * linefeed will scroll.  If ns is in the capability
144 			 * list this won't work.  We should probably have an
145 			 * sc capability but sf will generally take the place
146 			 * if it works.
147 			 *
148 			 * Superbee glitch: in the middle of the screen have to
149 			 * use esc B (down) because linefeed screws up in
150 			 * "Efficient Paging" (what a joke) mode (which is
151 			 * essential in some SB's because CRLF mode puts
152 			 * garbage in at end of memory), but you must use
153 			 * linefeed to scroll since down arrow won't go past
154 			 * memory end. I turned this off after recieving Paul
155 			 * Eggert's Superbee description which wins better. */
156 			if (cursor_down /* && !__tc_xb */ && __pfast)
157 				tputs(cursor_down, 0, __cputchar);
158 			else
159 				__cputchar('\n');
160 			l--;
161 			if (__pfast == 0)
162 				outcol = 0;
163 		}
164 	}
165 	if (destline < outline && !(cursor_address || cursor_up))
166 		destline = outline;
167 
168 	if (cursor_address &&
169 	    (cgp = tiparm(cursor_address, destline, destcol)))
170 	{
171 		/*
172 		 * Need this condition due to inconsistent behavior
173 		 * of backspace on the last column.
174 		 */
175 #ifdef DEBUG
176 		__CTRACE(__CTRACE_OUTPUT, "fgoto: cgp=%s\n", cgp);
177 #endif /* DEBUG */
178 		if (outcol != COLS - 1 &&
179 		    plod((int) strlen(cgp), in_refresh) > 0)
180 			plod(0, in_refresh);
181 		else
182 			tputs(cgp, 0, __cputchar);
183 	} else
184 		plod(0, in_refresh);
185 	outline = destline;
186 	outcol = destcol;
187 }
188 
189 /*
190  * Move (slowly) to destination.
191  * Hard thing here is using home cursor on really deficient terminals.
192  * Otherwise just use cursor motions, hacking use of tabs and overtabbing
193  * and backspace.
194  *
195  * XXX this needs to be revisited for wide characters since we may output
196  * XXX more than one byte for a character.
197  */
198 
199 static int plodcnt, plodflg;
200 
201 static int
202 plodput(int c)
203 {
204 	if (plodflg)
205 		--plodcnt;
206 	else
207 		__cputchar(c);
208 	return 0;
209 }
210 
211 static int
212 plod(int cnt, int in_refresh)
213 {
214 	int	 i, j, k, soutcol, soutline;
215 
216 #ifdef DEBUG
217 	__CTRACE(__CTRACE_OUTPUT, "plod: cnt=%d, in_refresh=%d\n",
218 	    cnt, in_refresh);
219 	__CTRACE(__CTRACE_OUTPUT, "plod: plodding from col %d, row %d to col %d, row %d\n",
220 	    outcol, outline, destcol, destline);
221 #endif /* DEBUG */
222 	plodcnt = plodflg = cnt;
223 	soutcol = outcol;
224 	soutline = outline;
225 
226 	/*
227 	 * Consider homing and moving down/right from there, vs. moving
228 	 * directly with local motions to the right spot.
229 	 */
230 	if (cursor_home) {
231 		/*
232 		 * i is the cost to home and tab/space to the right to get to
233 		 * the proper column.  This assumes nd space costs 1 char.  So
234 		 * i + destcol is cost of motion with home.
235 		 */
236 		if (__GT)
237 			i = (destcol / HARDTABS) + (destcol % HARDTABS);
238 		else
239 			i = destcol;
240 
241 		/* j is cost to move locally without homing. */
242 		if (destcol >= outcol) {	/* if motion is to the right */
243 			j = destcol / HARDTABS - outcol / HARDTABS;
244 			if (__GT && j)
245 				j += destcol % HARDTABS;
246 			else
247 				j = destcol - outcol;
248 		} else
249 			/* leftward motion only works if we can backspace. */
250 			if (outcol - destcol <= i)
251 				/* Cheaper to backspace. */
252 				i = j = outcol - destcol;
253 			else
254 				/* Impossibly expensive. */
255 				j = i + 1;
256 
257 		/* k is the absolute value of vertical distance. */
258 		k = outline - destline;
259 		if (k < 0)
260 			k = -k;
261 		j += k;
262 
263 		/* Decision.  We may not have a choice if no up. */
264 		if (i + destline < j || (!cursor_up && destline < outline)) {
265 			/*
266 			 * Cheaper to home.  Do it now and pretend it's a
267 			 * regular local motion.
268 			 */
269 			tputs(cursor_home, 0, plodput);
270 			outcol = outline = 0;
271 		} else
272 			if (cursor_to_ll) {
273 				/*
274 				 * Quickly consider homing down and moving from
275 				 * there.  Assume cost of ll is 2.
276 				 */
277 				k = (LINES - 1) - destline;
278 				if (i + k + 2 < j && (k <= 0 || cursor_up)) {
279 					tputs(cursor_to_ll, 0, plodput);
280 					outcol = 0;
281 					outline = LINES - 1;
282 				}
283 			}
284 	} else
285 		/* No home and no up means it's impossible. */
286 		if (!cursor_up && destline < outline)
287 			return (-1);
288 	if (__GT)
289 		i = destcol % HARDTABS + destcol / HARDTABS;
290 	else
291 		i = destcol;
292 #ifdef notdef
293 	if (back_tab && outcol > destcol &&
294 	    (j = (((outcol + 7) & ~7) - destcol - 1) >> 3)) {
295 		j *= (k = strlen(back_tab));
296 		if ((k += (destcol & 7)) > 4)
297 			j += 8 - (destcol & 7);
298 		else
299 			j += k;
300 	} else
301 #endif
302 		j = outcol - destcol;
303 
304 	/*
305 	 * If we will later need a \n which will turn into a \r\n by the
306 	 * system or the terminal, then don't bother to try to \r.
307 	 */
308 	if ((__NONL || !__pfast) && outline < destline)
309 		goto dontcr;
310 
311 	/*
312 	 * If the terminal will do a \r\n and there isn't room for it, then
313 	 * we can't afford a \r.
314 	 */
315 	if (!carriage_return && outline >= destline)
316 		goto dontcr;
317 
318 	/*
319 	 * If it will be cheaper, or if we can't back up, then send a return
320 	 * preliminarily.
321 	 */
322 	if (j > i + 1 || outcol > destcol) {
323 		/*
324 		 * BUG: this doesn't take the (possibly long) length of cr
325 		 * into account.
326 		 */
327 		if (carriage_return)
328 			tputs(carriage_return, 0, plodput);
329 		else
330 			plodput('\r');
331 		if (!carriage_return) {
332 			if (cursor_down)
333 				tputs(cursor_down, 0, plodput);
334 			else
335 				plodput('\n');
336 			outline++;
337 		}
338 
339 		outcol = 0;
340 	}
341 dontcr:while (outline < destline) {
342 		outline++;
343 		if (cursor_down)
344 			tputs(cursor_down, 0, plodput);
345 		else
346 			plodput('\n');
347 		if (plodcnt < 0)
348 			goto out;
349 		/*
350 		 * If the terminal does a CR with NL or we are in
351 		 * a mode where a \n will result in an implicit \r
352 		 * then adjust the outcol to match iff we actually
353 		 * emitted said \n.
354 		 */
355 		if ((__NONL || __pfast == 0) &&
356 		    (!cursor_down || (*cursor_down == '\n')))
357 			outcol = 0;
358 	}
359 #ifdef notdef
360 	if (back_tab)
361 		k = (int) strlen(back_tab);
362 #endif
363 	while (outcol > destcol) {
364 		if (plodcnt < 0)
365 			goto out;
366 #ifdef notdef
367 		if (back_tab && outcol - destcol > k + 4) {
368 			tputs(back_tab, 0, plodput);
369 			outcol--;
370 			outcol &= ~7;
371 			continue;
372 		}
373 #endif
374 		outcol--;
375 		if (cursor_left)
376 			tputs(cursor_left, 0, plodput);
377 		else
378 			plodput('\b');
379 	}
380 	while (outline > destline) {
381 		outline--;
382 		tputs(cursor_up, 0, plodput);
383 		if (plodcnt < 0)
384 			goto out;
385 	}
386 	if (__GT && destcol - outcol > 1) {
387 		for (;;) {
388 			i = tabcol(outcol, HARDTABS);
389 			if (i > destcol)
390 				break;
391 			if (tab)
392 				tputs(tab, 0, plodput);
393 			else
394 				plodput('\t');
395 			outcol = i;
396 		}
397 		if (destcol - outcol > 4 && i < COLS) {
398 			if (tab)
399 				tputs(tab, 0, plodput);
400 			else
401 				plodput('\t');
402 			outcol = i;
403 			while (outcol > destcol) {
404 				outcol--;
405 				if (cursor_left)
406 					tputs(cursor_left, 0, plodput);
407 				else
408 					plodput('\b');
409 			}
410 		}
411 	}
412 	while (outcol < destcol) {
413 		/*
414 		 * Move one char to the right.  We don't use nd space because
415 		 * it's better to just print the char we are moving over.
416 		 */
417 		if (in_refresh)
418 			if (plodflg)	/* Avoid a complex calculation. */
419 				plodcnt--;
420 			else {
421 #ifndef HAVE_WCHAR
422 				i = curscr->alines[outline]->line[outcol].ch
423 				    & __CHARTEXT;
424 				if (curscr->alines[outline]->line[outcol].attr
425 				    == curscr->wattr)
426 					__cputchar(i);
427 #else
428 				if ((curscr->alines[outline]->line[outcol].attr
429 				    & WA_ATTRIBUTES)
430 				    == curscr->wattr) {
431 					switch (WCOL(curscr->alines[outline]->line[outcol])) {
432 					case 1:
433 						__cputwchar(curscr->alines[outline]->line[outcol].ch);
434 						__cursesi_putnsp(curscr->alines[outline]->line[outcol].nsp,
435 								outline,
436 								outcol);
437 #ifdef DEBUG
438 						__CTRACE(__CTRACE_OUTPUT,
439 						    "plod: (%d,%d)WCOL(%d), "
440 						    "putwchar(%x)\n",
441 						    outline, outcol,
442 						    WCOL(curscr->alines[outline]->line[outcol]),
443 						    curscr->alines[outline]->line[outcol].ch);
444 #endif /* DEBUG */
445 					/*FALLTHROUGH*/
446 					case 0:
447 						break;
448 					default:
449 						goto nondes;
450 					}
451 				}
452 #endif /* HAVE_WCHAR */
453 				else
454 					goto nondes;
455 			}
456 		else
457 	nondes:	if (cursor_right)
458 			tputs(cursor_right, 0, plodput);
459 		else
460 			plodput(' ');
461 		outcol++;
462 		if (plodcnt < 0)
463 			goto out;
464 	}
465 
466 out:	if (plodflg) {
467 		outcol = soutcol;
468 		outline = soutline;
469 	}
470 #ifdef DEBUG
471 	__CTRACE(__CTRACE_OUTPUT, "plod: returns %d\n", plodcnt);
472 #endif /* DEBUG */
473 	return plodcnt;
474 }
475 
476 /*
477  * Return the column number that results from being in column col and
478  * hitting a tab, where tabs are set every ts columns.  Work right for
479  * the case where col > COLS, even if ts does not divide COLS.
480  */
481 static int
482 tabcol(int col, int ts)
483 {
484 	int	 offset;
485 
486 	if (col >= COLS) {
487 		offset = COLS * (col / COLS);
488 		col -= offset;
489 	} else
490 		offset = 0;
491 	return (col + ts - (col % ts) + offset);
492 }
493