Home | History | Annotate | Line # | Download | only in dist
      1 /* $XTermId: cursor.c,v 1.96 2025/01/04 00:58:54 tom Exp $ */
      2 
      3 /*
      4  * Copyright 2002-2024,2025 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  */
     67 void
     68 CursorSet(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 revised in 2023, using private mode 45 for movement within the
    114  * current (wrapped) line, and 1045 for movement to "any" line.
    115  */
    116 void
    117 CursorBack(XtermWidget xw, int n)
    118 {
    119 #define WRAP_MASK (REVERSEWRAP | WRAPAROUND)
    120 #define WRAP_MASK2 (REVERSEWRAP2 | WRAPAROUND)
    121     TScreen *screen = TScreenOf(xw);
    122     /* *INDENT-EQLS* */
    123     int rev    = (((xw->flags & WRAP_MASK) == WRAP_MASK) != 0);
    124     int rev2   = (((xw->flags & WRAP_MASK2) == WRAP_MASK2) != 0);
    125     int left   = ScrnLeftMargin(xw);
    126     int right  = ScrnRightMargin(xw);
    127     int before = screen->cur_col;
    128     int top    = ScrnTopMargin(xw);
    129     int bottom = ScrnBottomMargin(xw);
    130     int col    = screen->cur_col;
    131     int row    = screen->cur_row;
    132 
    133     int count;
    134     CLineData *ld;
    135 
    136     TRACE(("CursorBack(%d) current %d,%d rev=%d/%d margins H[%d..%d] V[%d..%d]\n",
    137 	   n,
    138 	   screen->cur_row, screen->cur_col,
    139 	   rev, rev2,
    140 	   left, right,
    141 	   top, bottom));
    142 
    143     /* if the cursor is already before the left-margin, we have to let it go */
    144     if (before < left)
    145 	left = 0;
    146 
    147     ld = NULL;
    148     if ((count = n) > 0) {
    149 	if ((rev || rev2) && screen->do_wrap) {
    150 	    --count;
    151 	} else {
    152 	    --col;
    153 	}
    154     }
    155 
    156     for (;;) {
    157 	if (col < left) {
    158 	    if (rev2) {
    159 		col = right;
    160 		if (row == top)
    161 		    row = bottom + 1;
    162 	    } else {
    163 		if (!rev) {
    164 		    col = left;
    165 		    break;
    166 		}
    167 		if (row <= top) {
    168 		    col = left;
    169 		    row = top;
    170 		    break;
    171 		}
    172 	    }
    173 	    ld = NULL;		/* try a reverse-wrap */
    174 	    if (--row <= 0) {
    175 		row = (top == 0) ? bottom : screen->max_row;
    176 	    }
    177 	}
    178 	if (ld == NULL) {
    179 	    ld = getLineData(screen, ROW2INX(screen, row));
    180 	    if (ld == NULL)
    181 		break;		/* should not happen */
    182 	    if (row != screen->cur_row) {
    183 		if (!rev2 && !LineTstWrapped(ld)) {
    184 		    ++row;	/* reverse-wrap failed */
    185 		    col = left;
    186 		    break;
    187 		}
    188 		col = right;
    189 	    }
    190 	}
    191 
    192 	if (--count <= 0)
    193 	    break;
    194 	--col;
    195     }
    196     set_cur_row(screen, row);
    197     set_cur_col(screen, col);
    198     do_xevents(xw);
    199 
    200     ResetWrap(screen);
    201 }
    202 
    203 /*
    204  * moves the cursor forward n, no wraparound
    205  */
    206 void
    207 CursorForward(XtermWidget xw, int n)
    208 {
    209     TScreen *screen = TScreenOf(xw);
    210 #if OPT_DEC_CHRSET
    211     LineData *ld = getLineData(screen, screen->cur_row);
    212 #endif
    213     int next = screen->cur_col + n;
    214     int max;
    215 
    216     if (IsLeftRightMode(xw)) {
    217 	max = screen->rgt_marg;
    218 	if (screen->cur_col > max)
    219 	    max = screen->max_col;
    220     } else {
    221 	max = LineMaxCol(screen, ld);
    222     }
    223 
    224     if (next > max)
    225 	next = max;
    226 
    227     set_cur_col(screen, next);
    228     ResetWrap(screen);
    229 }
    230 
    231 /*
    232  * moves the cursor down n, no scrolling.
    233  * Won't pass bottom margin or bottom of screen.
    234  */
    235 void
    236 CursorDown(TScreen *screen, int n)
    237 {
    238     int max;
    239     int next = screen->cur_row + n;
    240 
    241     max = (screen->cur_row > screen->bot_marg ?
    242 	   screen->max_row : screen->bot_marg);
    243     if (next > max)
    244 	next = max;
    245     if (next > screen->max_row)
    246 	next = screen->max_row;
    247 
    248     set_cur_row(screen, next);
    249     ResetWrap(screen);
    250 }
    251 
    252 /*
    253  * moves the cursor up n, no linestarving.
    254  * Won't pass top margin or top of screen.
    255  */
    256 void
    257 CursorUp(TScreen *screen, int n)
    258 {
    259     int min;
    260     int next = screen->cur_row - n;
    261 
    262     min = ((screen->cur_row < screen->top_marg)
    263 	   ? 0
    264 	   : screen->top_marg);
    265     if (next < min)
    266 	next = min;
    267     if (next < 0)
    268 	next = 0;
    269 
    270     set_cur_row(screen, next);
    271     ResetWrap(screen);
    272 }
    273 
    274 /*
    275  * Moves cursor down amount lines, scrolls if necessary.
    276  * Won't leave scrolling region. No carriage return.
    277  */
    278 void
    279 xtermIndex(XtermWidget xw, int amount)
    280 {
    281     TScreen *screen = TScreenOf(xw);
    282 
    283     /*
    284      * indexing when below scrolling region is cursor down.
    285      * if cursor high enough, no scrolling necessary.
    286      */
    287     if (screen->cur_row > screen->bot_marg
    288 	|| screen->cur_row + amount <= screen->bot_marg
    289 	|| (IsLeftRightMode(xw)
    290 	    && !ScrnIsColInMargins(screen, screen->cur_col))) {
    291 	CursorDown(screen, amount);
    292     } else {
    293 	int j;
    294 	CursorDown(screen, j = screen->bot_marg - screen->cur_row);
    295 	xtermScroll(xw, amount - j);
    296     }
    297 }
    298 
    299 /*
    300  * Moves cursor up amount lines, reverse scrolls if necessary.
    301  * Won't leave scrolling region. No carriage return.
    302  */
    303 void
    304 RevIndex(XtermWidget xw, int amount)
    305 {
    306     TScreen *screen = TScreenOf(xw);
    307 
    308     /*
    309      * reverse indexing when above scrolling region is cursor up.
    310      * if cursor low enough, no reverse indexing needed
    311      */
    312     if (screen->cur_row < screen->top_marg
    313 	|| screen->cur_row - amount >= screen->top_marg
    314 	|| (IsLeftRightMode(xw)
    315 	    && !ScrnIsColInMargins(screen, screen->cur_col))) {
    316 	CursorUp(screen, amount);
    317     } else {
    318 	RevScroll(xw, amount - (screen->cur_row - screen->top_marg));
    319 	CursorUp(screen, screen->cur_row - screen->top_marg);
    320     }
    321 }
    322 
    323 /*
    324  * Moves Cursor To First Column In Line
    325  * (Note: xterm doesn't implement SLH, SLL which would affect use of this)
    326  */
    327 void
    328 CarriageReturn(XtermWidget xw)
    329 {
    330     TScreen *screen = TScreenOf(xw);
    331     int left = ScrnLeftMargin(xw);
    332     int col;
    333 
    334     if (xw->flags & ORIGIN) {
    335 	col = left;
    336     } else if (screen->cur_col >= left) {
    337 	col = left;
    338     } else {
    339 	/*
    340 	 * If origin-mode is not active, it is possible to use cursor
    341 	 * addressing outside the margins.  In that case we will go to the
    342 	 * first column rather than following the margin.
    343 	 */
    344 	col = 0;
    345     }
    346 
    347     set_cur_col(screen, col);
    348     ResetWrap(screen);
    349     if (screen->jumpscroll && !screen->fastscroll)
    350 	do_xevents(xw);
    351 }
    352 
    353 /*
    354  * When resizing the window, if we're showing the alternate screen, we still
    355  * have to adjust the saved cursor from the normal screen to account for
    356  * shifting of the saved-line region in/out of the viewable window.
    357  */
    358 void
    359 AdjustSavedCursor(XtermWidget xw, int adjust)
    360 {
    361     TScreen *screen = TScreenOf(xw);
    362 
    363     if (screen->whichBuf) {
    364 	SavedCursor *sc = &screen->sc[0];
    365 
    366 	if (adjust > 0) {
    367 	    TRACE(("AdjustSavedCursor %d -> %d\n", sc->row, sc->row - adjust));
    368 	    sc->row += adjust;
    369 	}
    370     }
    371 }
    372 
    373 /*
    374  * Save Cursor and Attributes
    375  */
    376 void
    377 CursorSave2(XtermWidget xw, SavedCursor * sc)
    378 {
    379     TScreen *screen = TScreenOf(xw);
    380 
    381     sc->saved = True;
    382     sc->row = screen->cur_row;
    383     sc->col = screen->cur_col;
    384     sc->flags = xw->flags;
    385     sc->curgl = screen->curgl;
    386     sc->curgr = screen->curgr;
    387     sc->wrap_flag = screen->do_wrap;
    388 #if OPT_ISO_COLORS
    389     sc->cur_foreground = xw->cur_foreground;
    390     sc->cur_background = xw->cur_background;
    391     sc->sgr_foreground = xw->sgr_foreground;
    392     sc->sgr_38_xcolors = xw->sgr_38_xcolors;
    393 #endif
    394     saveCharsets(screen, sc->gsets);
    395 }
    396 
    397 void
    398 CursorSave(XtermWidget xw)
    399 {
    400     TScreen *screen = TScreenOf(xw);
    401     CursorSave2(xw, &screen->sc[screen->whichBuf]);
    402 }
    403 
    404 /*
    405  * We save/restore all visible attributes, plus wrapping, origin mode, and the
    406  * selective erase attribute.
    407  *
    408  * This is documented, but some of the documentation is incorrect.
    409  *
    410  * Page 270 of the VT420 manual (2nd edition) says that DECSC saves these
    411  * items:
    412  *
    413  * Cursor position
    414  * * Character attributes set by the SGR command
    415  * * Character sets (G0, G1, G2, or G3) currently in GL and GR
    416  * * Wrap flag (autowrap or no autowrap)
    417  * * State of origin mode (DECOM)
    418  * * Selective erase attribute
    419  * * Any single shift 2 (SS2) or single shift 3 (SS3) functions sent
    420  *
    421  * The VT520 manual has the same information (page 5-120).
    422  *
    423  * However, DEC 070 (29-June-1990), pages 5-186 to 5-191, describes
    424  * save/restore operations, but makes no mention of "wrap".
    425  *
    426  * Mattias Engdegrd, who has investigated wrapping behavior of different
    427  * terminals,
    428  *
    429  *	https://github.com/mattiase/wraptest
    430  *
    431  * states
    432  *	The LCF is saved/restored by the Save/Restore Cursor (DECSC/DECRC)
    433  *	control sequences.  The DECAWM flag is not included in the state
    434  *	managed by these operations.
    435  *
    436  * DEC 070 does mention the ANSI color text extension saying that it, too, is
    437  * saved/restored.
    438  */
    439 #define ALL_FLAGS (IFlags)(~0)
    440 #define DECSC_FLAGS (ATTRIBUTES|ORIGIN|PROTECTED)
    441 
    442 /*
    443  * Restore Cursor and Attributes
    444  */
    445 static void
    446 CursorRestoreFlags(XtermWidget xw, SavedCursor * sc, IFlags our_flags)
    447 {
    448     TScreen *screen = TScreenOf(xw);
    449 
    450     /* Restore the character sets, unless we never did a save-cursor op.
    451      * In that case, we'll reset the character sets.
    452      */
    453     if (sc->saved) {
    454 	restoreCharsets(screen, sc->gsets);
    455 	screen->curgl = sc->curgl;
    456 	screen->curgr = sc->curgr;
    457     } else {
    458 	resetCharsets(screen);
    459     }
    460 
    461     UIntClr(xw->flags, our_flags);
    462     UIntSet(xw->flags, sc->flags & our_flags);
    463     if ((xw->flags & ORIGIN)) {
    464 	CursorSet(screen,
    465 		  sc->row - screen->top_marg,
    466 		  ((xw->flags & LEFT_RIGHT)
    467 		   ? sc->col - screen->lft_marg
    468 		   : sc->col),
    469 		  xw->flags);
    470     } else {
    471 	CursorSet(screen, sc->row, sc->col, xw->flags);
    472     }
    473     screen->do_wrap = sc->wrap_flag;	/* after CursorSet/ResetWrap */
    474 
    475 #if OPT_ISO_COLORS
    476     xw->sgr_foreground = sc->sgr_foreground;
    477     xw->sgr_38_xcolors = sc->sgr_38_xcolors;
    478     SGR_Foreground(xw, (xw->flags & FG_COLOR) ? sc->cur_foreground : -1);
    479     SGR_Background(xw, (xw->flags & BG_COLOR) ? sc->cur_background : -1);
    480 #endif
    481 }
    482 
    483 /*
    484  * Use this entrypoint for the status-line.
    485  */
    486 void
    487 CursorRestore2(XtermWidget xw, SavedCursor * sc)
    488 {
    489     CursorRestoreFlags(xw, sc, ALL_FLAGS);
    490 }
    491 
    492 /*
    493  * Use this entrypoint for the VT100 window.
    494  */
    495 void
    496 CursorRestore(XtermWidget xw)
    497 {
    498     TScreen *screen = TScreenOf(xw);
    499     CursorRestoreFlags(xw, &screen->sc[screen->whichBuf], DECSC_FLAGS);
    500 }
    501 
    502 /*
    503  * Move the cursor to the first column of the n-th next line.
    504  */
    505 void
    506 CursorNextLine(XtermWidget xw, int count)
    507 {
    508     TScreen *screen = TScreenOf(xw);
    509 
    510     CursorDown(screen, count < 1 ? 1 : count);
    511     CarriageReturn(xw);
    512 }
    513 
    514 /*
    515  * Move the cursor to the first column of the n-th previous line.
    516  */
    517 void
    518 CursorPrevLine(XtermWidget xw, int count)
    519 {
    520     TScreen *screen = TScreenOf(xw);
    521 
    522     CursorUp(screen, count < 1 ? 1 : count);
    523     CarriageReturn(xw);
    524 }
    525 
    526 /*
    527  * Return col/row values which can be passed to CursorSet() preserving the
    528  * current col/row, e.g., accounting for DECOM.
    529  */
    530 int
    531 CursorCol(XtermWidget xw)
    532 {
    533     TScreen *screen = TScreenOf(xw);
    534     int result = screen->cur_col;
    535     if (xw->flags & ORIGIN) {
    536 	result -= ScrnLeftMargin(xw);
    537 	if (result < 0)
    538 	    result = 0;
    539     }
    540     return result;
    541 }
    542 
    543 int
    544 CursorRow(XtermWidget xw)
    545 {
    546     TScreen *screen = TScreenOf(xw);
    547     int result = screen->cur_row;
    548     if (xw->flags & ORIGIN) {
    549 	result -= screen->top_marg;
    550 	if (result < 0)
    551 	    result = 0;
    552     }
    553     return result;
    554 }
    555 
    556 #if OPT_TRACE
    557 int
    558 set_cur_row(TScreen *screen, int value)
    559 {
    560     TRACE(("set_cur_row %d vs %d\n", value, screen ? LastRowNumber(screen) : -1));
    561 
    562     assert(screen != NULL);
    563     assert(value >= 0);
    564     assert(value <= LastRowNumber(screen));
    565     if_STATUS_LINE(screen, {
    566 	value = LastRowNumber(screen);
    567     });
    568     screen->cur_row = value;
    569     return value;
    570 }
    571 
    572 int
    573 set_cur_col(TScreen *screen, int value)
    574 {
    575     TRACE(("set_cur_col %d vs %d\n", value, screen ? screen->max_col : -1));
    576 
    577     assert(screen != NULL);
    578     assert(value >= 0);
    579     assert(value <= screen->max_col);
    580     screen->cur_col = value;
    581     return value;
    582 }
    583 #endif /* OPT_TRACE */
    584 /*
    585  * vile:cmode fk=utf-8
    586  */
    587