cursor.c revision 5307cd1a
1/* $XTermId: cursor.c,v 1.88 2023/05/29 23:52:12 tom Exp $ */
2
3/*
4 * Copyright 2002-2022,2023 by Thomas E. Dickey
5 *
6 *                         All Rights Reserved
7 *
8 * Permission is hereby granted, free of charge, to any person obtaining a
9 * copy of this software and associated documentation files (the
10 * "Software"), to deal in the Software without restriction, including
11 * without limitation the rights to use, copy, modify, merge, publish,
12 * distribute, sublicense, and/or sell copies of the Software, and to
13 * permit persons to whom the Software is furnished to do so, subject to
14 * the following conditions:
15 *
16 * The above copyright notice and this permission notice shall be included
17 * in all copies or substantial portions of the Software.
18 *
19 * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS
20 * OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
21 * MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.
22 * IN NO EVENT SHALL THE ABOVE LISTED COPYRIGHT HOLDER(S) BE LIABLE FOR ANY
23 * CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT,
24 * TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE
25 * SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
26 *
27 * Except as contained in this notice, the name(s) of the above copyright
28 * holders shall not be used in advertising or otherwise to promote the
29 * sale, use or other dealings in this Software without prior written
30 * authorization.
31 *
32 * Copyright 1987 by Digital Equipment Corporation, Maynard, Massachusetts.
33 *
34 *                         All Rights Reserved
35 *
36 * Permission to use, copy, modify, and distribute this software and its
37 * documentation for any purpose and without fee is hereby granted,
38 * provided that the above copyright notice appear in all copies and that
39 * both that copyright notice and this permission notice appear in
40 * supporting documentation, and that the name of Digital Equipment
41 * Corporation not be used in advertising or publicity pertaining to
42 * distribution of the software without specific, written prior permission.
43 *
44 *
45 * DIGITAL DISCLAIMS ALL WARRANTIES WITH REGARD TO THIS SOFTWARE, INCLUDING
46 * ALL IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS, IN NO EVENT SHALL
47 * DIGITAL BE LIABLE FOR ANY SPECIAL, INDIRECT OR CONSEQUENTIAL DAMAGES OR
48 * ANY DAMAGES WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS,
49 * WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION,
50 * ARISING OUT OF OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS
51 * SOFTWARE.
52 */
53
54/* cursor.c */
55
56#include <xterm.h>
57#include <data.h>
58#include <menu.h>
59
60#include <assert.h>
61
62/*
63 * Moves the cursor to the specified position, checking for bounds.
64 * (this includes scrolling regions)
65 * The origin is considered to be 0, 0 for this procedure.
66 */
67void
68CursorSet(TScreen *screen, int row, int col, unsigned flags)
69{
70    int use_row = row;
71    int use_col = col;
72    int max_col = screen->max_col;
73    int max_row = screen->max_row;
74
75    if (flags & ORIGIN) {
76	use_col += screen->lft_marg;
77	max_col = screen->rgt_marg;
78    }
79    use_col = (use_col < 0 ? 0 : use_col);
80    set_cur_col(screen, (use_col <= max_col ? use_col : max_col));
81
82    if (flags & ORIGIN) {
83	use_row += screen->top_marg;
84	max_row = screen->bot_marg;
85    }
86    use_row = (use_row < 0 ? 0 : use_row);
87    set_cur_row(screen, (use_row <= max_row ? use_row : max_row));
88
89    ResetWrap(screen);
90
91    TRACE(("CursorSet(%d,%d) margins V[%d..%d] H[%d..%d] -> %d,%d %s\n",
92	   row, col,
93	   screen->top_marg,
94	   screen->bot_marg,
95	   screen->lft_marg,
96	   screen->rgt_marg,
97	   screen->cur_row,
98	   screen->cur_col,
99	   ((flags & ORIGIN) ? "origin" : "normal")));
100}
101
102/*
103 * Unlike VT100, xterm allows reverse wrapping of the cursor.  This feature was
104 * introduced in X10R4 (December 1986), but did not modify the comment which
105 * said "moves the cursor left n, no wrap around".  However, this reverse
106 * wrapping allowed the cursor to wrap around to the end of the screen.
107 *
108 * xterm added VT420-compatible left/right margin support in 2012.  If the
109 * cursor starts off within the margins, the reverse wrapping result will be
110 * within the margins.
111 *
112 * Wrapping to the end of the screen did not appear to be the original intent.
113 * That was suppressed in 2023.
114 */
115void
116CursorBack(XtermWidget xw, int n)
117{
118#define WRAP_MASK (REVERSEWRAP | WRAPAROUND)
119    TScreen *screen = TScreenOf(xw);
120    int rev = (((xw->flags & WRAP_MASK) == WRAP_MASK) != 0);
121    int left = ScrnLeftMargin(xw);
122    int right = ScrnRightMargin(xw);
123    int before = screen->cur_col;
124
125    CLineData *ld;
126    int count;
127    int top;
128    int col;
129    int row;
130
131    TRACE(("CursorBack(%d) current %d,%d rev=%d left=%d\n",
132	   n, screen->cur_row, screen->cur_col, rev, left));
133
134    if (rev && screen->do_wrap) {
135	n--;
136    }
137
138    /* if the cursor is already before the left-margin, we have to let it go */
139    if (before < left)
140	left = 0;
141
142    ld = NULL;
143    count = n;
144    top = 0;
145    col = screen->cur_col - 1;
146    row = screen->cur_row;
147
148    for (;;) {
149	if (col < left) {
150	    if (!rev) {
151		col = left;
152		break;
153	    }
154	    if (row <= top) {
155		col = left;
156		row = top;
157		break;
158	    }
159	    ld = NULL;		/* try a reverse-wrap */
160	    --row;
161	}
162	if (ld == NULL) {
163	    ld = getLineData(screen, ROW2INX(screen, row));
164	    if (ld == NULL)
165		break;		/* should not happen */
166	    if (row != screen->cur_row) {
167		if (!LineTstWrapped(ld)) {
168		    ++row;	/* reverse-wrap failed */
169		    col = left;
170		    break;
171		}
172		col = right;
173	    }
174	}
175
176	if (--count <= 0)
177	    break;
178	--col;
179    }
180    set_cur_row(screen, row);
181    set_cur_col(screen, col);
182    do_xevents(xw);
183
184    ResetWrap(screen);
185}
186
187/*
188 * moves the cursor forward n, no wraparound
189 */
190void
191CursorForward(XtermWidget xw, int n)
192{
193    TScreen *screen = TScreenOf(xw);
194#if OPT_DEC_CHRSET
195    LineData *ld = getLineData(screen, screen->cur_row);
196#endif
197    int next = screen->cur_col + n;
198    int max;
199
200    if (IsLeftRightMode(xw)) {
201	max = screen->rgt_marg;
202	if (screen->cur_col > max)
203	    max = screen->max_col;
204    } else {
205	max = LineMaxCol(screen, ld);
206    }
207
208    if (next > max)
209	next = max;
210
211    set_cur_col(screen, next);
212    ResetWrap(screen);
213}
214
215/*
216 * moves the cursor down n, no scrolling.
217 * Won't pass bottom margin or bottom of screen.
218 */
219void
220CursorDown(TScreen *screen, int n)
221{
222    int max;
223    int next = screen->cur_row + n;
224
225    max = (screen->cur_row > screen->bot_marg ?
226	   screen->max_row : screen->bot_marg);
227    if (next > max)
228	next = max;
229    if (next > screen->max_row)
230	next = screen->max_row;
231
232    set_cur_row(screen, next);
233    ResetWrap(screen);
234}
235
236/*
237 * moves the cursor up n, no linestarving.
238 * Won't pass top margin or top of screen.
239 */
240void
241CursorUp(TScreen *screen, int n)
242{
243    int min;
244    int next = screen->cur_row - n;
245
246    min = ((screen->cur_row < screen->top_marg)
247	   ? 0
248	   : screen->top_marg);
249    if (next < min)
250	next = min;
251    if (next < 0)
252	next = 0;
253
254    set_cur_row(screen, next);
255    ResetWrap(screen);
256}
257
258/*
259 * Moves cursor down amount lines, scrolls if necessary.
260 * Won't leave scrolling region. No carriage return.
261 */
262void
263xtermIndex(XtermWidget xw, int amount)
264{
265    TScreen *screen = TScreenOf(xw);
266
267    /*
268     * indexing when below scrolling region is cursor down.
269     * if cursor high enough, no scrolling necessary.
270     */
271    if (screen->cur_row > screen->bot_marg
272	|| screen->cur_row + amount <= screen->bot_marg
273	|| (IsLeftRightMode(xw)
274	    && !ScrnIsColInMargins(screen, screen->cur_col))) {
275	CursorDown(screen, amount);
276    } else {
277	int j;
278	CursorDown(screen, j = screen->bot_marg - screen->cur_row);
279	xtermScroll(xw, amount - j);
280    }
281}
282
283/*
284 * Moves cursor up amount lines, reverse scrolls if necessary.
285 * Won't leave scrolling region. No carriage return.
286 */
287void
288RevIndex(XtermWidget xw, int amount)
289{
290    TScreen *screen = TScreenOf(xw);
291
292    /*
293     * reverse indexing when above scrolling region is cursor up.
294     * if cursor low enough, no reverse indexing needed
295     */
296    if (screen->cur_row < screen->top_marg
297	|| screen->cur_row - amount >= screen->top_marg
298	|| (IsLeftRightMode(xw)
299	    && !ScrnIsColInMargins(screen, screen->cur_col))) {
300	CursorUp(screen, amount);
301    } else {
302	RevScroll(xw, amount - (screen->cur_row - screen->top_marg));
303	CursorUp(screen, screen->cur_row - screen->top_marg);
304    }
305}
306
307/*
308 * Moves Cursor To First Column In Line
309 * (Note: xterm doesn't implement SLH, SLL which would affect use of this)
310 */
311void
312CarriageReturn(XtermWidget xw)
313{
314    TScreen *screen = TScreenOf(xw);
315    int left = ScrnLeftMargin(xw);
316    int col;
317
318    if (xw->flags & ORIGIN) {
319	col = left;
320    } else if (screen->cur_col >= left) {
321	col = left;
322    } else {
323	/*
324	 * If origin-mode is not active, it is possible to use cursor
325	 * addressing outside the margins.  In that case we will go to the
326	 * first column rather than following the margin.
327	 */
328	col = 0;
329    }
330
331    set_cur_col(screen, col);
332    ResetWrap(screen);
333    do_xevents(xw);
334}
335
336/*
337 * When resizing the window, if we're showing the alternate screen, we still
338 * have to adjust the saved cursor from the normal screen to account for
339 * shifting of the saved-line region in/out of the viewable window.
340 */
341void
342AdjustSavedCursor(XtermWidget xw, int adjust)
343{
344    TScreen *screen = TScreenOf(xw);
345
346    if (screen->whichBuf) {
347	SavedCursor *sc = &screen->sc[0];
348
349	if (adjust > 0) {
350	    TRACE(("AdjustSavedCursor %d -> %d\n", sc->row, sc->row - adjust));
351	    sc->row += adjust;
352	}
353    }
354}
355
356/*
357 * Save Cursor and Attributes
358 */
359void
360CursorSave2(XtermWidget xw, SavedCursor * sc)
361{
362    TScreen *screen = TScreenOf(xw);
363
364    sc->saved = True;
365    sc->row = screen->cur_row;
366    sc->col = screen->cur_col;
367    sc->flags = xw->flags;
368    sc->curgl = screen->curgl;
369    sc->curgr = screen->curgr;
370    sc->wrap_flag = screen->do_wrap;
371#if OPT_ISO_COLORS
372    sc->cur_foreground = xw->cur_foreground;
373    sc->cur_background = xw->cur_background;
374    sc->sgr_foreground = xw->sgr_foreground;
375    sc->sgr_38_xcolors = xw->sgr_38_xcolors;
376#endif
377    saveCharsets(screen, sc->gsets);
378}
379
380void
381CursorSave(XtermWidget xw)
382{
383    TScreen *screen = TScreenOf(xw);
384    CursorSave2(xw, &screen->sc[screen->whichBuf]);
385}
386
387/*
388 * We save/restore all visible attributes, plus wrapping, origin mode, and the
389 * selective erase attribute.
390 *
391 * This is documented, but some of the documentation is incorrect.
392 *
393 * Page 270 of the VT420 manual (2nd edition) says that DECSC saves these
394 * items:
395 *
396 * Cursor position
397 * * Character attributes set by the SGR command
398 * * Character sets (G0, G1, G2, or G3) currently in GL and GR
399 * * Wrap flag (autowrap or no autowrap)
400 * * State of origin mode (DECOM)
401 * * Selective erase attribute
402 * * Any single shift 2 (SS2) or single shift 3 (SS3) functions sent
403 *
404 * The VT520 manual has the same information (page 5-120).
405 *
406 * However, DEC 070 (29-June-1990), pages 5-186 to 5-191, describes
407 * save/restore operations, but makes no mention of "wrap".
408 *
409 * Mattias Engdegård, who has investigated wrapping behavior of different
410 * terminals,
411 *
412 *	https://github.com/mattiase/wraptest
413 *
414 * states
415 *	The LCF is saved/restored by the Save/Restore Cursor (DECSC/DECRC)
416 *	control sequences.  The DECAWM flag is not included in the state
417 *	managed by these operations.
418 *
419 * DEC 070 does mention the ANSI color text extension saying that it, too, is
420 * saved/restored.
421 */
422#define ALL_FLAGS (IFlags)(~0)
423#define DECSC_FLAGS (ATTRIBUTES|ORIGIN|PROTECTED)
424
425/*
426 * Restore Cursor and Attributes
427 */
428static void
429CursorRestoreFlags(XtermWidget xw, SavedCursor * sc, IFlags our_flags)
430{
431    TScreen *screen = TScreenOf(xw);
432
433    /* Restore the character sets, unless we never did a save-cursor op.
434     * In that case, we'll reset the character sets.
435     */
436    if (sc->saved) {
437	restoreCharsets(screen, sc->gsets);
438	screen->curgl = sc->curgl;
439	screen->curgr = sc->curgr;
440    } else {
441	resetCharsets(screen);
442    }
443
444    UIntClr(xw->flags, our_flags);
445    UIntSet(xw->flags, sc->flags & our_flags);
446    if ((xw->flags & ORIGIN)) {
447	CursorSet(screen,
448		  sc->row - screen->top_marg,
449		  ((xw->flags & LEFT_RIGHT)
450		   ? sc->col - screen->lft_marg
451		   : sc->col),
452		  xw->flags);
453    } else {
454	CursorSet(screen, sc->row, sc->col, xw->flags);
455    }
456    screen->do_wrap = sc->wrap_flag;	/* after CursorSet/ResetWrap */
457
458#if OPT_ISO_COLORS
459    xw->sgr_foreground = sc->sgr_foreground;
460    xw->sgr_38_xcolors = sc->sgr_38_xcolors;
461    SGR_Foreground(xw, (xw->flags & FG_COLOR) ? sc->cur_foreground : -1);
462    SGR_Background(xw, (xw->flags & BG_COLOR) ? sc->cur_background : -1);
463#endif
464}
465
466/*
467 * Use this entrypoint for the status-line.
468 */
469void
470CursorRestore2(XtermWidget xw, SavedCursor * sc)
471{
472    CursorRestoreFlags(xw, sc, ALL_FLAGS);
473}
474
475/*
476 * Use this entrypoint for the VT100 window.
477 */
478void
479CursorRestore(XtermWidget xw)
480{
481    TScreen *screen = TScreenOf(xw);
482    CursorRestoreFlags(xw, &screen->sc[screen->whichBuf], DECSC_FLAGS);
483}
484
485/*
486 * Move the cursor to the first column of the n-th next line.
487 */
488void
489CursorNextLine(XtermWidget xw, int count)
490{
491    TScreen *screen = TScreenOf(xw);
492
493    CursorDown(screen, count < 1 ? 1 : count);
494    CarriageReturn(xw);
495    do_xevents(xw);
496}
497
498/*
499 * Move the cursor to the first column of the n-th previous line.
500 */
501void
502CursorPrevLine(XtermWidget xw, int count)
503{
504    TScreen *screen = TScreenOf(xw);
505
506    CursorUp(screen, count < 1 ? 1 : count);
507    CarriageReturn(xw);
508    do_xevents(xw);
509}
510
511/*
512 * Return col/row values which can be passed to CursorSet() preserving the
513 * current col/row, e.g., accounting for DECOM.
514 */
515int
516CursorCol(XtermWidget xw)
517{
518    TScreen *screen = TScreenOf(xw);
519    int result = screen->cur_col;
520    if (xw->flags & ORIGIN) {
521	result -= ScrnLeftMargin(xw);
522	if (result < 0)
523	    result = 0;
524    }
525    return result;
526}
527
528int
529CursorRow(XtermWidget xw)
530{
531    TScreen *screen = TScreenOf(xw);
532    int result = screen->cur_row;
533    if (xw->flags & ORIGIN) {
534	result -= screen->top_marg;
535	if (result < 0)
536	    result = 0;
537    }
538    return result;
539}
540
541#if OPT_TRACE
542int
543set_cur_row(TScreen *screen, int value)
544{
545    TRACE(("set_cur_row %d vs %d\n", value, screen ? LastRowNumber(screen) : -1));
546
547    assert(screen != 0);
548    assert(value >= 0);
549    assert(value <= LastRowNumber(screen));
550    if_STATUS_LINE(screen, {
551	value = LastRowNumber(screen);
552    });
553    screen->cur_row = value;
554    return value;
555}
556
557int
558set_cur_col(TScreen *screen, int value)
559{
560    TRACE(("set_cur_col %d vs %d\n", value, screen ? screen->max_col : -1));
561
562    assert(screen != 0);
563    assert(value >= 0);
564    assert(value <= screen->max_col);
565    screen->cur_col = value;
566    return value;
567}
568#endif /* OPT_TRACE */
569/*
570 * vile:cmode fk=utf-8
571 */
572