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 */ 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 revised in 2023, using private mode 45 for movement within the 114 * current (wrapped) line, and 1045 for movement to "any" line. 115 */ 116void 117CursorBack(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 */ 206void 207CursorForward(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 */ 235void 236CursorDown(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 */ 256void 257CursorUp(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 */ 278void 279xtermIndex(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 */ 303void 304RevIndex(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 */ 327void 328CarriageReturn(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 */ 358void 359AdjustSavedCursor(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 */ 376void 377CursorSave2(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 397void 398CursorSave(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 Engdegård, 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 */ 445static void 446CursorRestoreFlags(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 */ 486void 487CursorRestore2(XtermWidget xw, SavedCursor * sc) 488{ 489 CursorRestoreFlags(xw, sc, ALL_FLAGS); 490} 491 492/* 493 * Use this entrypoint for the VT100 window. 494 */ 495void 496CursorRestore(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 */ 505void 506CursorNextLine(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 */ 517void 518CursorPrevLine(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 */ 530int 531CursorCol(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 543int 544CursorRow(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 557int 558set_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 572int 573set_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