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