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
37static 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. */
57int
58mvcur(int ly, int lx, int y, int x)
59{
60	return (__mvcur(ly, lx, y, x, 0));
61}
62
63static void fgoto(int);
64static int plod(int, int);
65static int plodput(int);
66static int tabcol(int, int);
67
68static 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 */
75int
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
89static void
90fgoto(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
195static int plodcnt, plodflg;
196#ifdef HAVE_WCHAR
197static char s[MB_LEN_MAX];
198#endif
199
200static int
201plodput(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
220static int
221plod(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	}
350dontcr: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
493out:	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 */
506static int
507tabcol(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