xref: /netbsd-src/lib/libcurses/cr_put.c (revision ee6c5161ec58f3c42e85c7decec7836b00067515)
1 /*	$NetBSD: cr_put.c,v 1.40 2022/10/19 06:09:27 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 #include <limits.h>
34 #include <stdlib.h>
35 #ifndef lint
36 #if 0
37 static char sccsid[] = "@(#)cr_put.c	8.3 (Berkeley) 5/4/94";
38 #else
39 __RCSID("$NetBSD: cr_put.c,v 1.40 2022/10/19 06:09:27 blymn Exp $");
40 #endif
41 #endif				/* not lint */
42 
43 #include <string.h>
44 
45 #include "curses.h"
46 #include "curses_private.h"
47 
48 #define	HARDTABS	8
49 
50 /*
51  * Terminal driving and line formatting routines.  Basic motion optimizations
52  * are done here as well as formatting lines (printing of control characters,
53  * line numbering and the like).
54  */
55 
56 /* Stub function for the users. */
57 int
mvcur(int ly,int lx,int y,int x)58 mvcur(int ly, int lx, int y, int x)
59 {
60 	return (__mvcur(ly, lx, y, x, 0));
61 }
62 
63 static void fgoto(int);
64 static int plod(int, int);
65 static int plodput(int);
66 static int tabcol(int, int);
67 
68 static int outcol, outline, destcol, destline;
69 
70 /*
71  * Sync the position of the output cursor.  Most work here is rounding for
72  * terminal boundaries getting the column position implied by wraparound or
73  * the lack thereof and rolling up the screen to get destline on the screen.
74  */
75 int
__mvcur(int ly,int lx,int y,int x,int in_refresh)76 __mvcur(int ly, int lx, int y, int x, int in_refresh)
77 {
78 	__CTRACE(__CTRACE_OUTPUT,
79 	    "mvcur: moving cursor from (%d, %d) to (%d, %d) in refresh %d\n",
80 	    ly, lx, y, x, in_refresh);
81 	destcol = x;
82 	destline = y;
83 	outcol = lx;
84 	outline = ly;
85 	fgoto(in_refresh);
86 	return (OK);
87 }
88 
89 static void
fgoto(int in_refresh)90 fgoto(int in_refresh)
91 {
92 	int	 c, l;
93 	char	*cgp;
94 
95 	__CTRACE(__CTRACE_OUTPUT, "fgoto: in_refresh=%d\n", in_refresh);
96 	__CTRACE(__CTRACE_OUTPUT,
97 	    "fgoto: outcol=%d, outline=%d, destcol=%d, destline=%d\n",
98 	    outcol, outline, destcol, destline);
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 receiving 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 		__CTRACE(__CTRACE_OUTPUT, "fgoto: cgp=%s\n", cgp);
176 		if (outcol != COLS - 1 &&
177 		    plod((int) strlen(cgp), in_refresh) > 0)
178 			plod(0, in_refresh);
179 		else
180 			tputs(cgp, 0, __cputchar);
181 	} else
182 		plod(0, in_refresh);
183 	outline = destline;
184 	outcol = destcol;
185 }
186 
187 /*
188  * Move (slowly) to destination.
189  * Hard thing here is using home cursor on really deficient terminals.
190  * Otherwise just use cursor motions, hacking use of tabs and overtabbing
191  * and backspace.
192  *
193  */
194 
195 static int plodcnt, plodflg;
196 #ifdef HAVE_WCHAR
197 static char s[MB_LEN_MAX];
198 #endif
199 
200 static int
plodput(int c)201 plodput(int c)
202 {
203 	if (plodflg) {
204 		int cw;
205 
206 #ifdef HAVE_WCHAR
207 		cw = wctomb(s, c);
208 		if (cw < 0)
209 			cw = 1;
210 #else
211 		cw = 1;
212 #endif /* HAVE_WCHAR */
213 
214 		plodcnt -= cw;
215 	} else
216 		__cputchar(c);
217 	return 0;
218 }
219 
220 static int
plod(int cnt,int in_refresh)221 plod(int cnt, int in_refresh)
222 {
223 	int	 i, j, k, soutcol, soutline;
224 	__LDATA  *csp;
225 
226 	__CTRACE(__CTRACE_OUTPUT, "plod: cnt=%d, in_refresh=%d\n",
227 	    cnt, in_refresh);
228 	__CTRACE(__CTRACE_OUTPUT,
229 	    "plod: plodding from col %d, row %d to col %d, row %d\n",
230 	    outcol, outline, destcol, destline);
231 	plodcnt = plodflg = cnt;
232 	soutcol = outcol;
233 	soutline = outline;
234 
235 	/*
236 	 * Consider homing and moving down/right from there, vs. moving
237 	 * directly with local motions to the right spot.
238 	 */
239 	if (cursor_home) {
240 		/*
241 		 * i is the cost to home and tab/space to the right to get to
242 		 * the proper column.  This assumes nd space costs 1 char.  So
243 		 * i + destcol is cost of motion with home.
244 		 */
245 		if (__GT)
246 			i = (destcol / HARDTABS) + (destcol % HARDTABS);
247 		else
248 			i = destcol;
249 
250 		/* j is cost to move locally without homing. */
251 		if (destcol >= outcol) {	/* if motion is to the right */
252 			j = destcol / HARDTABS - outcol / HARDTABS;
253 			if (__GT && j)
254 				j += destcol % HARDTABS;
255 			else
256 				j = destcol - outcol;
257 		} else
258 			/* leftward motion only works if we can backspace. */
259 			if (outcol - destcol <= i)
260 				/* Cheaper to backspace. */
261 				i = j = outcol - destcol;
262 			else
263 				/* Impossibly expensive. */
264 				j = i + 1;
265 
266 		/* k is the absolute value of vertical distance. */
267 		k = outline - destline;
268 		if (k < 0)
269 			k = -k;
270 		j += k;
271 
272 		/* Decision.  We may not have a choice if no up. */
273 		if (i + destline < j || (!cursor_up && destline < outline)) {
274 			/*
275 			 * Cheaper to home.  Do it now and pretend it's a
276 			 * regular local motion.
277 			 */
278 			tputs(cursor_home, 0, plodput);
279 			outcol = outline = 0;
280 		} else
281 			if (cursor_to_ll) {
282 				/*
283 				 * Quickly consider homing down and moving from
284 				 * there.  Assume cost of ll is 2.
285 				 */
286 				k = (LINES - 1) - destline;
287 				if (i + k + 2 < j && (k <= 0 || cursor_up)) {
288 					tputs(cursor_to_ll, 0, plodput);
289 					outcol = 0;
290 					outline = LINES - 1;
291 				}
292 			}
293 	} else
294 		/* No home and no up means it's impossible. */
295 		if (!cursor_up && destline < outline)
296 			return (-1);
297 	if (__GT)
298 		i = destcol % HARDTABS + destcol / HARDTABS;
299 	else
300 		i = destcol;
301 #ifdef notdef
302 	if (back_tab && outcol > destcol &&
303 	    (j = (((outcol + 7) & ~7) - destcol - 1) >> 3)) {
304 		j *= (k = strlen(back_tab));
305 		if ((k += (destcol & 7)) > 4)
306 			j += 8 - (destcol & 7);
307 		else
308 			j += k;
309 	} else
310 #endif
311 		j = outcol - destcol;
312 
313 	/*
314 	 * If we will later need a \n which will turn into a \r\n by the
315 	 * system or the terminal, then don't bother to try to \r.
316 	 */
317 	if ((__NONL || !__pfast) && outline < destline)
318 		goto dontcr;
319 
320 	/*
321 	 * If the terminal will do a \r\n and there isn't room for it, then
322 	 * we can't afford a \r.
323 	 */
324 	if (!carriage_return && outline >= destline)
325 		goto dontcr;
326 
327 	/*
328 	 * If it will be cheaper, or if we can't back up, then send a return
329 	 * preliminarily.
330 	 */
331 	if (j > i + 1 || outcol > destcol) {
332 		/*
333 		 * BUG: this doesn't take the (possibly long) length of cr
334 		 * into account.
335 		 */
336 		if (carriage_return)
337 			tputs(carriage_return, 0, plodput);
338 		else
339 			plodput('\r');
340 		if (!carriage_return) {
341 			if (cursor_down)
342 				tputs(cursor_down, 0, plodput);
343 			else
344 				plodput('\n');
345 			outline++;
346 		}
347 
348 		outcol = 0;
349 	}
350 dontcr:while (outline < destline) {
351 		outline++;
352 		if (cursor_down)
353 			tputs(cursor_down, 0, plodput);
354 		else
355 			plodput('\n');
356 		if (plodcnt < 0)
357 			goto out;
358 		/*
359 		 * If the terminal does a CR with NL or we are in
360 		 * a mode where a \n will result in an implicit \r
361 		 * then adjust the outcol to match iff we actually
362 		 * emitted said \n.
363 		 */
364 		if ((__NONL || __pfast == 0) &&
365 		    (!cursor_down || (*cursor_down == '\n')))
366 			outcol = 0;
367 	}
368 #ifdef notdef
369 	if (back_tab)
370 		k = (int) strlen(back_tab);
371 #endif
372 	while (outcol > destcol) {
373 		if (plodcnt < 0)
374 			goto out;
375 #ifdef notdef
376 		if (back_tab && outcol - destcol > k + 4) {
377 			tputs(back_tab, 0, plodput);
378 			outcol--;
379 			outcol &= ~7;
380 			continue;
381 		}
382 #endif
383 		outcol--;
384 		if (cursor_left)
385 			tputs(cursor_left, 0, plodput);
386 		else
387 			plodput('\b');
388 	}
389 	while (outline > destline) {
390 		outline--;
391 		tputs(cursor_up, 0, plodput);
392 		if (plodcnt < 0)
393 			goto out;
394 	}
395 	if (__GT && destcol - outcol > 1) {
396 		for (;;) {
397 			i = tabcol(outcol, HARDTABS);
398 			if (i > destcol)
399 				break;
400 			if (tab)
401 				tputs(tab, 0, plodput);
402 			else
403 				plodput('\t');
404 			outcol = i;
405 		}
406 		if (destcol - outcol > 4 && i < COLS) {
407 			if (tab)
408 				tputs(tab, 0, plodput);
409 			else
410 				plodput('\t');
411 			outcol = i;
412 			while (outcol > destcol) {
413 				outcol--;
414 				if (cursor_left)
415 					tputs(cursor_left, 0, plodput);
416 				else
417 					plodput('\b');
418 			}
419 		}
420 	}
421 
422 #ifdef HAVE_WCHAR
423 	/*
424 	 * If destcol is halfway through a multicolumn
425 	 * wide char, we have no chance of plodding.
426 	 */
427 	if (curscr->alines[outline]->line[outcol].cflags & CA_CONTINUATION) {
428 		plodcnt = -1;
429 		goto out;
430 	}
431 #endif /* HAVE_WCHAR */
432 
433 	while (outcol < destcol) {
434 		int chw;
435 
436 		csp = &curscr->alines[outline]->line[outcol];
437 #ifdef HAVE_WCHAR
438 		chw = csp->wcols;
439 #else
440 		chw = 1;
441 #endif /* HAVE_WCHAR */
442 
443 
444 		/*
445 		 * Move one char to the right.  We don't use nd space because
446 		 * it's better to just print the char we are moving over.
447 		 */
448 		if (in_refresh)
449 			if (plodflg)	/* Avoid a complex calculation. */
450 				plodcnt--;
451 			else {
452 #ifndef HAVE_WCHAR
453 				i = csp->ch & __CHARTEXT;
454 				if (csp->attr == curscr->wattr)
455 					__cputchar(i);
456 #else
457 				if ((csp->attr & WA_ATTRIBUTES)
458 				    == curscr->wattr) {
459 					if (csp->cflags & CA_CONTINUATION)
460 						goto nondes;
461 
462 					if (csp->wcols >= 1) {
463 						__cputwchar(csp->ch);
464 						__cursesi_putnsp(csp->nsp,
465 								outline,
466 								outcol);
467 						__CTRACE(__CTRACE_OUTPUT,
468 						    "plod: (%d,%d)wcols(%d), "
469 						    "putwchar(%x)\n",
470 						    outline, outcol,
471 						    csp->wcols, csp->ch);
472 					}
473 
474 					if (csp->wcols == 0)
475 						break;
476 				}
477 #endif /* HAVE_WCHAR */
478 				else
479 					goto nondes;
480 			}
481 		else {
482 		nondes:	if (cursor_right)
483 				tputs(cursor_right, 0, plodput);
484 			else
485 				plodput(' ');
486 		}
487 
488 		outcol += chw;
489 		if (plodcnt < 0)
490 			goto out;
491 	}
492 
493 out:	if (plodflg) {
494 		outcol = soutcol;
495 		outline = soutline;
496 	}
497 	__CTRACE(__CTRACE_OUTPUT, "plod: returns %d\n", plodcnt);
498 	return plodcnt;
499 }
500 
501 /*
502  * Return the column number that results from being in column col and
503  * hitting a tab, where tabs are set every ts columns.  Work right for
504  * the case where col > COLS, even if ts does not divide COLS.
505  */
506 static int
tabcol(int col,int ts)507 tabcol(int col, int ts)
508 {
509 	int	 offset;
510 
511 	if (col >= COLS) {
512 		offset = COLS * (col / COLS);
513 		col -= offset;
514 	} else
515 		offset = 0;
516 	return (col + ts - (col % ts) + offset);
517 }
518