move.c (83661B)
1 // move.c: Functions for moving the cursor and scrolling text. 2 // 3 // There are two ways to move the cursor: 4 // 1. Move the cursor directly, the text is scrolled to keep the cursor in the 5 // window. 6 // 2. Scroll the text, the cursor is moved into the text visible in the 7 // window. 8 // The 'scrolloff' option makes this a bit complicated. 9 10 #include <assert.h> 11 #include <limits.h> 12 #include <stdbool.h> 13 #include <stdint.h> 14 #include <stdlib.h> 15 16 #include "nvim/ascii_defs.h" 17 #include "nvim/buffer.h" 18 #include "nvim/buffer_defs.h" 19 #include "nvim/cursor.h" 20 #include "nvim/decoration.h" 21 #include "nvim/diff.h" 22 #include "nvim/drawscreen.h" 23 #include "nvim/edit.h" 24 #include "nvim/errors.h" 25 #include "nvim/eval/typval.h" 26 #include "nvim/eval/window.h" 27 #include "nvim/fold.h" 28 #include "nvim/getchar.h" 29 #include "nvim/gettext_defs.h" 30 #include "nvim/globals.h" 31 #include "nvim/grid.h" 32 #include "nvim/grid_defs.h" 33 #include "nvim/macros_defs.h" 34 #include "nvim/mark_defs.h" 35 #include "nvim/mbyte.h" 36 #include "nvim/memline.h" 37 #include "nvim/message.h" 38 #include "nvim/mouse.h" 39 #include "nvim/move.h" 40 #include "nvim/normal.h" 41 #include "nvim/normal_defs.h" 42 #include "nvim/option.h" 43 #include "nvim/option_vars.h" 44 #include "nvim/plines.h" 45 #include "nvim/popupmenu.h" 46 #include "nvim/pos_defs.h" 47 #include "nvim/strings.h" 48 #include "nvim/types_defs.h" 49 #include "nvim/vim_defs.h" 50 #include "nvim/window.h" 51 #include "nvim/winfloat.h" 52 53 typedef struct { 54 linenr_T lnum; // line number 55 int fill; // filler lines 56 int height; // height of added line 57 } lineoff_T; 58 59 #include "move.c.generated.h" 60 61 /// Get the number of screen lines skipped with "wp->w_skipcol". 62 static int adjust_plines_for_skipcol(win_T *wp) 63 { 64 if (wp->w_skipcol == 0) { 65 return 0; 66 } 67 68 int width = wp->w_view_width - win_col_off(wp); 69 int w2 = width + win_col_off2(wp); 70 if (wp->w_skipcol >= width && w2 > 0) { 71 return (wp->w_skipcol - width) / w2 + 1; 72 } 73 74 return 0; 75 } 76 77 /// Return how many lines "lnum" will take on the screen, taking into account 78 /// whether it is the first line, whether w_skipcol is non-zero and limiting to 79 /// the window height. 80 int plines_correct_topline(win_T *wp, linenr_T lnum, linenr_T *nextp, bool limit_winheight, 81 bool *foldedp) 82 { 83 int n = plines_win_full(wp, lnum, nextp, foldedp, true, false); 84 if (lnum == wp->w_topline) { 85 n -= adjust_plines_for_skipcol(wp); 86 } 87 if (limit_winheight && n > wp->w_view_height) { 88 return wp->w_view_height; 89 } 90 return n; 91 } 92 93 // Compute wp->w_botline for the current wp->w_topline. Can be called after 94 // wp->w_topline changed. 95 static void comp_botline(win_T *wp) 96 { 97 linenr_T lnum; 98 int done; 99 100 // If w_cline_row is valid, start there. 101 // Otherwise have to start at w_topline. 102 check_cursor_moved(wp); 103 if (wp->w_valid & VALID_CROW) { 104 lnum = wp->w_cursor.lnum; 105 done = wp->w_cline_row; 106 } else { 107 lnum = wp->w_topline; 108 done = 0; 109 } 110 111 for (; lnum <= wp->w_buffer->b_ml.ml_line_count; lnum++) { 112 linenr_T last = lnum; 113 bool folded; 114 int n = plines_correct_topline(wp, lnum, &last, true, &folded); 115 if (lnum <= wp->w_cursor.lnum && last >= wp->w_cursor.lnum) { 116 wp->w_cline_row = done; 117 wp->w_cline_height = n; 118 wp->w_cline_folded = folded; 119 redraw_for_cursorline(wp); 120 wp->w_valid |= (VALID_CROW|VALID_CHEIGHT); 121 } 122 if (done + n > wp->w_view_height) { 123 break; 124 } 125 done += n; 126 lnum = last; 127 } 128 129 // wp->w_botline is the line that is just below the window 130 wp->w_botline = lnum; 131 wp->w_valid |= VALID_BOTLINE|VALID_BOTLINE_AP; 132 wp->w_viewport_invalid = true; 133 134 set_empty_rows(wp, done); 135 136 win_check_anchored_floats(wp); 137 } 138 139 /// Redraw when w_cline_row changes and 'relativenumber' or 'cursorline' is set. 140 /// Also when concealing is on and 'concealcursor' is not active. 141 static void redraw_for_cursorline(win_T *wp) 142 FUNC_ATTR_NONNULL_ALL 143 { 144 if ((wp->w_valid & VALID_CROW) == 0 && !pum_visible() 145 && (wp->w_p_rnu || win_cursorline_standout(wp))) { 146 // win_line() will redraw the number column and cursorline only. 147 redraw_later(wp, UPD_VALID); 148 } 149 } 150 151 /// Redraw when 'concealcursor' is active, or when w_virtcol changes and: 152 /// - 'cursorcolumn' is set, or 153 /// - 'cursorlineopt' contains "screenline", or 154 /// - Visual mode is active. 155 static void redraw_for_cursorcolumn(win_T *wp) 156 FUNC_ATTR_NONNULL_ALL 157 { 158 // If the cursor moves horizontally when 'concealcursor' is active, then the 159 // current line needs to be redrawn to calculate the correct cursor position. 160 if (wp == curwin && wp->w_p_cole > 0 && conceal_cursor_line(wp)) { 161 redrawWinline(wp, wp->w_cursor.lnum); 162 } 163 164 if ((wp->w_valid & VALID_VIRTCOL) || pum_visible()) { 165 return; 166 } 167 168 if (wp->w_p_cuc) { 169 // When 'cursorcolumn' is set need to redraw with UPD_SOME_VALID. 170 redraw_later(wp, UPD_SOME_VALID); 171 } else if (wp->w_p_cul && (wp->w_p_culopt_flags & kOptCuloptFlagScreenline)) { 172 // When 'cursorlineopt' contains "screenline" need to redraw with UPD_VALID. 173 redraw_later(wp, UPD_VALID); 174 } 175 176 // When current buffer's cursor moves in Visual mode, redraw it with UPD_INVERTED. 177 if (VIsual_active && wp->w_buffer == curbuf) { 178 redraw_buf_later(curbuf, UPD_INVERTED); 179 } 180 } 181 182 /// Set wp->w_virtcol to a value ("vcol") that is already valid. 183 /// Handles redrawing if wp->w_virtcol was previously invalid. 184 void set_valid_virtcol(win_T *wp, colnr_T vcol) 185 { 186 wp->w_virtcol = vcol; 187 redraw_for_cursorcolumn(wp); 188 wp->w_valid |= VALID_VIRTCOL; 189 } 190 191 /// Calculates how much the 'listchars' "precedes" or 'smoothscroll' "<<<" 192 /// marker overlaps with buffer text for window "wp". 193 /// Parameter "extra2" should be the padding on the 2nd line, not the first 194 /// line. When "extra2" is -1 calculate the padding. 195 /// Returns the number of columns of overlap with buffer text, excluding the 196 /// extra padding on the ledge. 197 int sms_marker_overlap(win_T *wp, int extra2) 198 { 199 if (extra2 == -1) { 200 extra2 = win_col_off(wp) - win_col_off2(wp); 201 } 202 // There is no marker overlap when in showbreak mode, thus no need to 203 // account for it. See wlv_put_linebuf(). 204 if (*get_showbreak_value(wp) != NUL) { 205 return 0; 206 } 207 208 // Overlap when 'list' and 'listchars' "precedes" are set is 1. 209 if (wp->w_p_list && wp->w_p_lcs_chars.prec) { 210 return 1; 211 } 212 213 return extra2 > 3 ? 0 : 3 - extra2; 214 } 215 216 /// Calculates the skipcol offset for window "wp" given how many 217 /// physical lines we want to scroll down. 218 static int skipcol_from_plines(win_T *wp, int plines_off) 219 { 220 int width1 = wp->w_view_width - win_col_off(wp); 221 222 int skipcol = 0; 223 if (plines_off > 0) { 224 skipcol += width1; 225 } 226 if (plines_off > 1) { 227 skipcol += (width1 + win_col_off2(wp)) * (plines_off - 1); 228 } 229 return skipcol; 230 } 231 232 /// Set wp->w_skipcol to zero and redraw later if needed. 233 static void reset_skipcol(win_T *wp) 234 { 235 if (wp->w_skipcol == 0) { 236 return; 237 } 238 239 wp->w_skipcol = 0; 240 241 // Should use the least expensive way that displays all that changed. 242 // UPD_NOT_VALID is too expensive, UPD_REDRAW_TOP does not redraw 243 // enough when the top line gets another screen line. 244 redraw_later(wp, UPD_SOME_VALID); 245 } 246 247 // Update wp->w_topline to move the cursor onto the screen. 248 void update_topline(win_T *wp) 249 { 250 bool check_botline = false; 251 OptInt *so_ptr = wp->w_p_so >= 0 ? &wp->w_p_so : &p_so; 252 OptInt save_so = *so_ptr; 253 254 // Cursor is updated instead when this is true for 'splitkeep'. 255 if (skip_update_topline) { 256 return; 257 } 258 259 // If there is no valid screen and when the window height is zero just use 260 // the cursor line. 261 if (!default_grid.chars || wp->w_view_height == 0) { 262 check_cursor_lnum(wp); 263 wp->w_topline = wp->w_cursor.lnum; 264 wp->w_botline = wp->w_topline; 265 wp->w_viewport_invalid = true; 266 wp->w_scbind_pos = 1; 267 return; 268 } 269 270 check_cursor_moved(wp); 271 if (wp->w_valid & VALID_TOPLINE) { 272 return; 273 } 274 275 // When dragging with the mouse, don't scroll that quickly 276 if (mouse_dragging > 0) { 277 *so_ptr = mouse_dragging - 1; 278 } 279 280 linenr_T old_topline = wp->w_topline; 281 int old_topfill = wp->w_topfill; 282 283 // If the buffer is empty, always set topline to 1. 284 if (buf_is_empty(wp->w_buffer)) { // special case - file is empty 285 if (wp->w_topline != 1) { 286 redraw_later(wp, UPD_NOT_VALID); 287 } 288 wp->w_topline = 1; 289 wp->w_botline = 2; 290 wp->w_skipcol = 0; 291 wp->w_valid |= VALID_BOTLINE|VALID_BOTLINE_AP; 292 wp->w_viewport_invalid = true; 293 wp->w_scbind_pos = 1; 294 } else { 295 bool check_topline = false; 296 // If the cursor is above or near the top of the window, scroll the window 297 // to show the line the cursor is in, with 'scrolloff' context. 298 if (wp->w_topline > 1 || wp->w_skipcol > 0) { 299 // If the cursor is above topline, scrolling is always needed. 300 // If the cursor is far below topline and there is no folding, 301 // scrolling down is never needed. 302 if (wp->w_cursor.lnum < wp->w_topline) { 303 check_topline = true; 304 } else if (check_top_offset(wp)) { 305 check_topline = true; 306 } else if (wp->w_skipcol > 0 && wp->w_cursor.lnum == wp->w_topline) { 307 colnr_T vcol; 308 309 // Check that the cursor position is visible. Add columns for 310 // the marker displayed in the top-left if needed. 311 getvvcol(wp, &wp->w_cursor, &vcol, NULL, NULL); 312 int overlap = sms_marker_overlap(wp, -1); 313 if (wp->w_skipcol + overlap > vcol) { 314 check_topline = true; 315 } 316 } 317 } 318 // Check if there are more filler lines than allowed. 319 if (!check_topline && wp->w_topfill > win_get_fill(wp, wp->w_topline)) { 320 check_topline = true; 321 } 322 323 if (check_topline) { 324 int halfheight = wp->w_view_height / 2 - 1; 325 if (halfheight < 2) { 326 halfheight = 2; 327 } 328 int64_t n; 329 if (win_lines_concealed(wp)) { 330 // Count the number of logical lines between the cursor and 331 // topline + p_so (approximation of how much will be 332 // scrolled). 333 n = 0; 334 for (linenr_T lnum = wp->w_cursor.lnum; lnum < wp->w_topline + *so_ptr; lnum++) { 335 // stop at end of file or when we know we are far off 336 assert(wp->w_buffer != 0); 337 if (lnum >= wp->w_buffer->b_ml.ml_line_count 338 || (n += !decor_conceal_line(wp, lnum, false)) >= halfheight) { 339 break; 340 } 341 hasFolding(wp, lnum, NULL, &lnum); 342 } 343 } else { 344 n = wp->w_topline + *so_ptr - wp->w_cursor.lnum; 345 } 346 347 // If we weren't very close to begin with, we scroll to put the 348 // cursor in the middle of the window. Otherwise put the cursor 349 // near the top of the window. 350 if (n >= halfheight) { 351 scroll_cursor_halfway(wp, false, false); 352 } else { 353 scroll_cursor_top(wp, scrolljump_value(wp), false); 354 check_botline = true; 355 } 356 } else { 357 // Make sure topline is the first line of a fold. 358 hasFolding(wp, wp->w_topline, &wp->w_topline, NULL); 359 check_botline = true; 360 } 361 } 362 363 // If the cursor is below the bottom of the window, scroll the window 364 // to put the cursor on the window. 365 // When w_botline is invalid, recompute it first, to avoid a redraw later. 366 // If w_botline was approximated, we might need a redraw later in a few 367 // cases, but we don't want to spend (a lot of) time recomputing w_botline 368 // for every small change. 369 if (check_botline) { 370 if (!(wp->w_valid & VALID_BOTLINE_AP)) { 371 validate_botline_win(wp); 372 } 373 374 assert(wp->w_buffer != 0); 375 if (wp->w_botline <= wp->w_buffer->b_ml.ml_line_count) { 376 if (wp->w_cursor.lnum < wp->w_botline) { 377 if ((wp->w_cursor.lnum >= wp->w_botline - *so_ptr || win_lines_concealed(wp))) { 378 lineoff_T loff; 379 380 // Cursor is (a few lines) above botline, check if there are 381 // 'scrolloff' window lines below the cursor. If not, need to 382 // scroll. 383 int n = wp->w_empty_rows; 384 loff.lnum = wp->w_cursor.lnum; 385 // In a fold go to its last line. 386 hasFolding(wp, loff.lnum, NULL, &loff.lnum); 387 loff.fill = 0; 388 n += wp->w_filler_rows; 389 loff.height = 0; 390 while (loff.lnum < wp->w_botline 391 && (loff.lnum + 1 < wp->w_botline || loff.fill == 0)) { 392 n += loff.height; 393 if (n >= *so_ptr) { 394 break; 395 } 396 botline_forw(wp, &loff); 397 } 398 if (n >= *so_ptr) { 399 // sufficient context, no need to scroll 400 check_botline = false; 401 } 402 } else { 403 // sufficient context, no need to scroll 404 check_botline = false; 405 } 406 } 407 if (check_botline) { 408 int n = 0; 409 if (win_lines_concealed(wp)) { 410 // Count the number of logical lines between the cursor and 411 // botline - p_so (approximation of how much will be 412 // scrolled). 413 for (linenr_T lnum = wp->w_cursor.lnum; lnum >= wp->w_botline - *so_ptr; lnum--) { 414 // stop at end of file or when we know we are far off 415 if (lnum <= 0 416 || (n += !decor_conceal_line(wp, lnum - 1, false)) > wp->w_view_height + 1) { 417 break; 418 } 419 hasFolding(wp, lnum, &lnum, NULL); 420 } 421 } else { 422 n = wp->w_cursor.lnum - wp->w_botline + 1 + (int)(*so_ptr); 423 } 424 if (n <= wp->w_view_height + 1) { 425 scroll_cursor_bot(wp, scrolljump_value(wp), false); 426 } else { 427 scroll_cursor_halfway(wp, false, false); 428 } 429 } 430 } 431 } 432 wp->w_valid |= VALID_TOPLINE; 433 wp->w_viewport_invalid = true; 434 win_check_anchored_floats(wp); 435 436 // Need to redraw when topline changed. 437 if (wp->w_topline != old_topline 438 || wp->w_topfill != old_topfill) { 439 dollar_vcol = -1; 440 redraw_later(wp, UPD_VALID); 441 442 // When 'smoothscroll' is not set, should reset w_skipcol. 443 if (!wp->w_p_sms) { 444 reset_skipcol(wp); 445 } else if (wp->w_skipcol != 0) { 446 redraw_later(wp, UPD_SOME_VALID); 447 } 448 449 // May need to set w_skipcol when cursor in w_topline. 450 if (wp->w_cursor.lnum == wp->w_topline) { 451 validate_cursor(wp); 452 } 453 } 454 455 *so_ptr = save_so; 456 } 457 458 /// Return the scrolljump value to use for the window "wp". 459 /// When 'scrolljump' is positive use it as-is. 460 /// When 'scrolljump' is negative use it as a percentage of the window height. 461 static int scrolljump_value(win_T *wp) 462 { 463 int result = p_sj >= 0 ? (int)p_sj : (wp->w_view_height * (int)(-p_sj)) / 100; 464 return result; 465 } 466 467 /// Return true when there are not 'scrolloff' lines above the cursor for window "wp". 468 static bool check_top_offset(win_T *wp) 469 { 470 int so = get_scrolloff_value(wp); 471 if (wp->w_cursor.lnum < wp->w_topline + so || win_lines_concealed(wp)) { 472 lineoff_T loff; 473 loff.lnum = wp->w_cursor.lnum; 474 loff.fill = 0; 475 int n = wp->w_topfill; // always have this context 476 // Count the visible screen lines above the cursor line. 477 while (n < so) { 478 topline_back(wp, &loff); 479 // Stop when included a line above the window. 480 if (loff.lnum < wp->w_topline 481 || (loff.lnum == wp->w_topline && loff.fill > 0)) { 482 break; 483 } 484 n += loff.height; 485 } 486 if (n < so) { 487 return true; 488 } 489 } 490 return false; 491 } 492 493 /// Update w_curswant. 494 void update_curswant_force(void) 495 { 496 validate_virtcol(curwin); 497 curwin->w_curswant = curwin->w_virtcol; 498 curwin->w_set_curswant = false; 499 } 500 501 /// Update w_curswant if w_set_curswant is set. 502 void update_curswant(void) 503 { 504 if (curwin->w_set_curswant) { 505 update_curswant_force(); 506 } 507 } 508 509 // Check if the cursor has moved. Set the w_valid flag accordingly. 510 void check_cursor_moved(win_T *wp) 511 { 512 if (wp->w_cursor.lnum != wp->w_valid_cursor.lnum) { 513 wp->w_valid &= ~(VALID_WROW|VALID_WCOL|VALID_VIRTCOL 514 |VALID_CHEIGHT|VALID_CROW|VALID_TOPLINE); 515 516 // Concealed line visibility toggled. 517 if (wp == curwin && wp->w_valid_cursor.lnum > 0 && wp->w_p_cole >= 2 518 && !conceal_cursor_line(wp) 519 && (decor_conceal_line(wp, wp->w_cursor.lnum - 1, true) 520 || decor_conceal_line(wp, wp->w_valid_cursor.lnum - 1, true))) { 521 changed_window_setting(wp); 522 } 523 wp->w_valid_cursor = wp->w_cursor; 524 wp->w_valid_leftcol = wp->w_leftcol; 525 wp->w_valid_skipcol = wp->w_skipcol; 526 wp->w_viewport_invalid = true; 527 } else if (wp->w_skipcol != wp->w_valid_skipcol) { 528 wp->w_valid &= ~(VALID_WROW|VALID_WCOL|VALID_VIRTCOL 529 |VALID_CHEIGHT|VALID_CROW 530 |VALID_BOTLINE|VALID_BOTLINE_AP); 531 wp->w_valid_cursor = wp->w_cursor; 532 wp->w_valid_leftcol = wp->w_leftcol; 533 wp->w_valid_skipcol = wp->w_skipcol; 534 } else if (wp->w_cursor.col != wp->w_valid_cursor.col 535 || wp->w_leftcol != wp->w_valid_leftcol 536 || wp->w_cursor.coladd != 537 wp->w_valid_cursor.coladd) { 538 wp->w_valid &= ~(VALID_WROW|VALID_WCOL|VALID_VIRTCOL); 539 wp->w_valid_cursor.col = wp->w_cursor.col; 540 wp->w_valid_leftcol = wp->w_leftcol; 541 wp->w_valid_cursor.coladd = wp->w_cursor.coladd; 542 wp->w_viewport_invalid = true; 543 } 544 } 545 546 // Call this function when some window settings have changed, which require 547 // the cursor position, botline and topline to be recomputed and the window to 548 // be redrawn. E.g, when changing the 'wrap' option or folding. 549 void changed_window_setting(win_T *wp) 550 { 551 wp->w_lines_valid = 0; 552 changed_line_abv_curs_win(wp); 553 wp->w_valid &= ~(VALID_BOTLINE|VALID_BOTLINE_AP|VALID_TOPLINE); 554 redraw_later(wp, UPD_NOT_VALID); 555 } 556 557 /// Call changed_window_setting() for every window. 558 void changed_window_setting_all(void) 559 { 560 FOR_ALL_TAB_WINDOWS(tp, wp) { 561 changed_window_setting(wp); 562 } 563 } 564 565 // Set wp->w_topline to a certain number. 566 void set_topline(win_T *wp, linenr_T lnum) 567 { 568 linenr_T prev_topline = wp->w_topline; 569 570 // go to first of folded lines 571 hasFolding(wp, lnum, &lnum, NULL); 572 // Approximate the value of w_botline 573 wp->w_botline += lnum - wp->w_topline; 574 if (wp->w_botline > wp->w_buffer->b_ml.ml_line_count + 1) { 575 wp->w_botline = wp->w_buffer->b_ml.ml_line_count + 1; 576 } 577 wp->w_topline = lnum; 578 wp->w_topline_was_set = true; 579 if (lnum != prev_topline) { 580 // Keep the filler lines when the topline didn't change. 581 wp->w_topfill = 0; 582 } 583 wp->w_valid &= ~(VALID_WROW|VALID_CROW|VALID_BOTLINE|VALID_TOPLINE); 584 // Don't set VALID_TOPLINE here, 'scrolloff' needs to be checked. 585 redraw_later(wp, UPD_VALID); 586 } 587 588 /// Call this function when the length of the cursor line (in screen 589 /// characters) has changed, and the change is before the cursor. 590 /// If the line length changed the number of screen lines might change, 591 /// requiring updating w_topline. That may also invalidate w_crow. 592 /// Need to take care of w_botline separately! 593 void changed_cline_bef_curs(win_T *wp) 594 { 595 wp->w_valid &= ~(VALID_WROW|VALID_WCOL|VALID_VIRTCOL|VALID_CROW 596 |VALID_CHEIGHT|VALID_TOPLINE); 597 } 598 599 // Call this function when the length of a line (in screen characters) above 600 // the cursor have changed. 601 // Need to take care of w_botline separately! 602 void changed_line_abv_curs(void) 603 { 604 curwin->w_valid &= ~(VALID_WROW|VALID_WCOL|VALID_VIRTCOL|VALID_CROW 605 |VALID_CHEIGHT|VALID_TOPLINE); 606 } 607 608 void changed_line_abv_curs_win(win_T *wp) 609 { 610 wp->w_valid &= ~(VALID_WROW|VALID_WCOL|VALID_VIRTCOL|VALID_CROW 611 |VALID_CHEIGHT|VALID_TOPLINE); 612 } 613 614 /// Make sure the value of wp->w_botline is valid. 615 void validate_botline_win(win_T *wp) 616 FUNC_ATTR_NONNULL_ALL 617 { 618 if (!(wp->w_valid & VALID_BOTLINE)) { 619 comp_botline(wp); 620 } 621 } 622 623 /// Mark wp->w_botline as invalid (because of some change in the buffer). 624 void invalidate_botline_win(win_T *wp) 625 FUNC_ATTR_NONNULL_ALL 626 { 627 wp->w_valid &= ~(VALID_BOTLINE|VALID_BOTLINE_AP); 628 } 629 630 void approximate_botline_win(win_T *wp) 631 { 632 wp->w_valid &= ~VALID_BOTLINE; 633 } 634 635 // Return true if wp->w_wrow and wp->w_wcol are valid. 636 int cursor_valid(win_T *wp) 637 { 638 check_cursor_moved(wp); 639 return (wp->w_valid & (VALID_WROW|VALID_WCOL)) == (VALID_WROW|VALID_WCOL); 640 } 641 642 // Validate cursor position. Makes sure w_wrow and w_wcol are valid. 643 // w_topline must be valid, you may need to call update_topline() first! 644 void validate_cursor(win_T *wp) 645 { 646 check_cursor_lnum(wp); 647 check_cursor_moved(wp); 648 if ((wp->w_valid & (VALID_WCOL|VALID_WROW)) != (VALID_WCOL|VALID_WROW)) { 649 curs_columns(wp, true); 650 } 651 } 652 653 // Compute wp->w_cline_row and wp->w_cline_height, based on the current value 654 // of wp->w_topline. 655 static void curs_rows(win_T *wp) 656 { 657 // Check if wp->w_lines[].wl_size is invalid 658 bool all_invalid = (!redrawing() 659 || wp->w_lines_valid == 0 660 || wp->w_lines[0].wl_lnum > wp->w_topline); 661 int i = 0; 662 wp->w_cline_row = 0; 663 for (linenr_T lnum = wp->w_topline; lnum < wp->w_cursor.lnum; i++) { 664 bool valid = false; 665 if (!all_invalid && i < wp->w_lines_valid) { 666 if (wp->w_lines[i].wl_lnum < lnum || !wp->w_lines[i].wl_valid) { 667 continue; // skip changed or deleted lines 668 } 669 if (wp->w_lines[i].wl_lnum == lnum) { 670 // Check for newly inserted lines below this row, in which 671 // case we need to check for folded lines. 672 if (!wp->w_buffer->b_mod_set 673 || wp->w_lines[i].wl_lastlnum < wp->w_cursor.lnum 674 || wp->w_buffer->b_mod_top 675 > wp->w_lines[i].wl_lastlnum + 1) { 676 valid = true; 677 } 678 } else if (wp->w_lines[i].wl_lnum > lnum) { 679 i--; // hold at inserted lines 680 } 681 } 682 if (valid && (lnum != wp->w_topline || (wp->w_skipcol == 0 && !win_may_fill(wp)))) { 683 lnum = wp->w_lines[i].wl_lastlnum + 1; 684 // Cursor inside folded or concealed lines, don't count this row 685 if (lnum > wp->w_cursor.lnum) { 686 break; 687 } 688 wp->w_cline_row += wp->w_lines[i].wl_size; 689 } else { 690 linenr_T last = lnum; 691 bool folded; 692 int n = plines_correct_topline(wp, lnum, &last, true, &folded); 693 lnum = last + 1; 694 if (lnum + decor_conceal_line(wp, lnum - 1, false) > wp->w_cursor.lnum) { 695 break; 696 } 697 wp->w_cline_row += n; 698 } 699 } 700 701 check_cursor_moved(wp); 702 if (!(wp->w_valid & VALID_CHEIGHT)) { 703 if (all_invalid 704 || i == wp->w_lines_valid 705 || (i < wp->w_lines_valid 706 && (!wp->w_lines[i].wl_valid 707 || wp->w_lines[i].wl_lnum != wp->w_cursor.lnum))) { 708 wp->w_cline_height = plines_win_full(wp, wp->w_cursor.lnum, NULL, 709 &wp->w_cline_folded, true, true); 710 } else if (i > wp->w_lines_valid) { 711 // a line that is too long to fit on the last screen line 712 wp->w_cline_height = 0; 713 wp->w_cline_folded = hasFolding(wp, wp->w_cursor.lnum, NULL, NULL); 714 } else { 715 wp->w_cline_height = wp->w_lines[i].wl_size; 716 wp->w_cline_folded = wp->w_lines[i].wl_folded; 717 } 718 } 719 720 redraw_for_cursorline(wp); 721 wp->w_valid |= VALID_CROW|VALID_CHEIGHT; 722 } 723 724 // Validate wp->w_virtcol only. 725 void validate_virtcol(win_T *wp) 726 { 727 check_cursor_moved(wp); 728 729 if (wp->w_valid & VALID_VIRTCOL) { 730 return; 731 } 732 733 getvvcol(wp, &wp->w_cursor, NULL, &(wp->w_virtcol), NULL); 734 redraw_for_cursorcolumn(wp); 735 wp->w_valid |= VALID_VIRTCOL; 736 } 737 738 // Validate wp->w_cline_height only. 739 void validate_cheight(win_T *wp) 740 { 741 check_cursor_moved(wp); 742 743 if (wp->w_valid & VALID_CHEIGHT) { 744 return; 745 } 746 747 wp->w_cline_height = plines_win_full(wp, wp->w_cursor.lnum, 748 NULL, &wp->w_cline_folded, 749 true, true); 750 wp->w_valid |= VALID_CHEIGHT; 751 } 752 753 // Validate w_wcol and w_virtcol only. 754 void validate_cursor_col(win_T *wp) 755 { 756 validate_virtcol(wp); 757 758 if (wp->w_valid & VALID_WCOL) { 759 return; 760 } 761 762 colnr_T col = wp->w_virtcol; 763 colnr_T off = win_col_off(wp); 764 col += off; 765 int width = wp->w_view_width - off + win_col_off2(wp); 766 767 // long line wrapping, adjust wp->w_wrow 768 if (wp->w_p_wrap && col >= (colnr_T)wp->w_view_width && width > 0) { 769 // use same formula as what is used in curs_columns() 770 col -= ((col - wp->w_view_width) / width + 1) * width; 771 } 772 if (col > (int)wp->w_leftcol) { 773 col -= wp->w_leftcol; 774 } else { 775 col = 0; 776 } 777 wp->w_wcol = col; 778 779 wp->w_valid |= VALID_WCOL; 780 } 781 782 // Compute offset of a window, occupied by absolute or relative line number, 783 // fold column and sign column (these don't move when scrolling horizontally). 784 int win_col_off(win_T *wp) 785 { 786 return ((wp->w_p_nu || wp->w_p_rnu || *wp->w_p_stc != NUL) 787 ? (number_width(wp) + (*wp->w_p_stc == NUL)) : 0) 788 + ((wp != cmdwin_win) ? 0 : 1) 789 + win_fdccol_count(wp) + (wp->w_scwidth * SIGN_WIDTH); 790 } 791 792 // Return the difference in column offset for the second screen line of a 793 // wrapped line. It's positive if 'number' or 'relativenumber' is on and 'n' 794 // is in 'cpoptions'. 795 int win_col_off2(win_T *wp) 796 { 797 if ((wp->w_p_nu || wp->w_p_rnu || *wp->w_p_stc != NUL) 798 && vim_strchr(p_cpo, CPO_NUMCOL) != NULL) { 799 return number_width(wp) + (*wp->w_p_stc == NUL); 800 } 801 return 0; 802 } 803 804 // Compute wp->w_wcol and wp->w_virtcol. 805 // Also updates wp->w_wrow and wp->w_cline_row. 806 // Also updates wp->w_leftcol. 807 // @param may_scroll when true, may scroll horizontally 808 void curs_columns(win_T *wp, int may_scroll) 809 { 810 colnr_T startcol; 811 colnr_T endcol; 812 813 // First make sure that w_topline is valid (after moving the cursor). 814 update_topline(wp); 815 816 // Next make sure that w_cline_row is valid. 817 if (!(wp->w_valid & VALID_CROW)) { 818 curs_rows(wp); 819 } 820 821 // Compute the number of virtual columns. 822 if (wp->w_cline_folded) { 823 // In a folded line the cursor is always in the first column 824 startcol = wp->w_virtcol = endcol = wp->w_leftcol; 825 } else { 826 getvvcol(wp, &wp->w_cursor, &startcol, &(wp->w_virtcol), &endcol); 827 } 828 829 // remove '$' from change command when cursor moves onto it 830 if (startcol > dollar_vcol) { 831 dollar_vcol = -1; 832 } 833 834 int extra = win_col_off(wp); 835 wp->w_wcol = wp->w_virtcol + extra; 836 endcol += extra; 837 838 // Now compute w_wrow, counting screen lines from w_cline_row. 839 wp->w_wrow = wp->w_cline_row; 840 841 int n; 842 int width1 = wp->w_view_width - extra; // text width for first screen line 843 int width2 = 0; // text width for second and later screen line 844 bool did_sub_skipcol = false; 845 if (width1 <= 0) { 846 // No room for text, put cursor in last char of window. 847 // If not wrapping, the last non-empty line. 848 wp->w_wcol = wp->w_view_width - 1; 849 if (wp->w_p_wrap) { 850 wp->w_wrow = wp->w_view_height - 1; 851 } else { 852 wp->w_wrow = wp->w_view_height - 1 - wp->w_empty_rows; 853 } 854 } else if (wp->w_p_wrap && wp->w_view_width != 0) { 855 width2 = width1 + win_col_off2(wp); 856 857 // skip columns that are not visible 858 if (wp->w_cursor.lnum == wp->w_topline 859 && wp->w_skipcol > 0 860 && wp->w_wcol >= wp->w_skipcol) { 861 // Deduct by multiples of width2. This allows the long line wrapping 862 // formula below to correctly calculate the w_wcol value when wrapping. 863 if (wp->w_skipcol <= width1) { 864 wp->w_wcol -= width2; 865 } else { 866 wp->w_wcol -= width2 * (((wp->w_skipcol - width1) / width2) + 1); 867 } 868 869 did_sub_skipcol = true; 870 } 871 872 // long line wrapping, adjust wp->w_wrow 873 if (wp->w_wcol >= wp->w_view_width) { 874 // this same formula is used in validate_cursor_col() 875 n = (wp->w_wcol - wp->w_view_width) / width2 + 1; 876 wp->w_wcol -= n * width2; 877 wp->w_wrow += n; 878 } 879 } else if (may_scroll 880 && !wp->w_cline_folded) { 881 // No line wrapping: compute wp->w_leftcol if scrolling is on and line 882 // is not folded. 883 // If scrolling is off, wp->w_leftcol is assumed to be 0 884 885 // If Cursor is left of the screen, scroll rightwards. 886 // If Cursor is right of the screen, scroll leftwards 887 // If we get closer to the edge than 'sidescrolloff', scroll a little 888 // extra 889 int siso = get_sidescrolloff_value(wp); 890 int off_left = startcol - wp->w_leftcol - siso; 891 int off_right = endcol - wp->w_leftcol - wp->w_view_width + siso + 1; 892 if (off_left < 0 || off_right > 0) { 893 int diff = (off_left < 0) ? -off_left : off_right; 894 895 // When far off or not enough room on either side, put cursor in 896 // middle of window. 897 int new_leftcol; 898 if (p_ss == 0 || diff >= width1 / 2 || off_right >= off_left) { 899 new_leftcol = wp->w_wcol - extra - width1 / 2; 900 } else { 901 if (diff < p_ss) { 902 assert(p_ss <= INT_MAX); 903 diff = (int)p_ss; 904 } 905 if (off_left < 0) { 906 new_leftcol = wp->w_leftcol - diff; 907 } else { 908 new_leftcol = wp->w_leftcol + diff; 909 } 910 } 911 new_leftcol = MAX(new_leftcol, 0); 912 if (new_leftcol != (int)wp->w_leftcol) { 913 wp->w_leftcol = new_leftcol; 914 win_check_anchored_floats(wp); 915 // screen has to be redrawn with new wp->w_leftcol 916 redraw_later(wp, UPD_NOT_VALID); 917 } 918 } 919 wp->w_wcol -= wp->w_leftcol; 920 } else if (wp->w_wcol > (int)wp->w_leftcol) { 921 wp->w_wcol -= wp->w_leftcol; 922 } else { 923 wp->w_wcol = 0; 924 } 925 926 // Skip over filler lines. At the top use w_topfill, there 927 // may be some filler lines above the window. 928 if (wp->w_cursor.lnum == wp->w_topline) { 929 wp->w_wrow += wp->w_topfill; 930 } else { 931 wp->w_wrow += win_get_fill(wp, wp->w_cursor.lnum); 932 } 933 934 int plines = 0; 935 int so = get_scrolloff_value(wp); 936 colnr_T prev_skipcol = wp->w_skipcol; 937 if ((wp->w_wrow >= wp->w_view_height 938 || ((prev_skipcol > 0 939 || wp->w_wrow + so >= wp->w_view_height) 940 && (plines = plines_win_nofill(wp, wp->w_cursor.lnum, false)) - 1 941 >= wp->w_view_height)) 942 && wp->w_view_height != 0 943 && wp->w_cursor.lnum == wp->w_topline 944 && width2 > 0 945 && wp->w_view_width != 0) { 946 // Cursor past end of screen. Happens with a single line that does 947 // not fit on screen. Find a skipcol to show the text around the 948 // cursor. Avoid scrolling all the time. compute value of "extra": 949 // 1: Less than "p_so" lines above 950 // 2: Less than "p_so" lines below 951 // 3: both of them 952 extra = 0; 953 if (wp->w_skipcol + so * width2 > wp->w_virtcol) { 954 extra = 1; 955 } 956 // Compute last display line of the buffer line that we want at the 957 // bottom of the window. 958 if (plines == 0) { 959 plines = plines_win(wp, wp->w_cursor.lnum, false); 960 } 961 plines--; 962 if (plines > wp->w_wrow + so) { 963 assert(so <= INT_MAX); 964 n = wp->w_wrow + so; 965 } else { 966 n = plines; 967 } 968 if ((colnr_T)n >= wp->w_view_height + wp->w_skipcol / width2 - so) { 969 extra += 2; 970 } 971 972 if (extra == 3 || wp->w_view_height <= so * 2) { 973 // not enough room for 'scrolloff', put cursor in the middle 974 n = wp->w_virtcol / width2; 975 if (n > wp->w_view_height / 2) { 976 n -= wp->w_view_height / 2; 977 } else { 978 n = 0; 979 } 980 // don't skip more than necessary 981 if (n > plines - wp->w_view_height + 1) { 982 n = plines - wp->w_view_height + 1; 983 } 984 wp->w_skipcol = n > 0 ? width1 + (n - 1) * width2 985 : 0; 986 } else if (extra == 1) { 987 // less than 'scrolloff' lines above, decrease skipcol 988 assert(so <= INT_MAX); 989 extra = (wp->w_skipcol + so * width2 - wp->w_virtcol + width2 - 1) / width2; 990 if (extra > 0) { 991 if ((colnr_T)(extra * width2) > wp->w_skipcol) { 992 extra = wp->w_skipcol / width2; 993 } 994 wp->w_skipcol -= extra * width2; 995 } 996 } else if (extra == 2) { 997 // less than 'scrolloff' lines below, increase skipcol 998 endcol = (n - wp->w_view_height + 1) * width2; 999 while (endcol > wp->w_virtcol) { 1000 endcol -= width2; 1001 } 1002 wp->w_skipcol = MAX(wp->w_skipcol, endcol); 1003 } 1004 1005 // adjust w_wrow for the changed w_skipcol 1006 if (did_sub_skipcol) { 1007 wp->w_wrow -= (wp->w_skipcol - prev_skipcol) / width2; 1008 } else { 1009 wp->w_wrow -= wp->w_skipcol / width2; 1010 } 1011 1012 if (wp->w_wrow >= wp->w_view_height) { 1013 // small window, make sure cursor is in it 1014 extra = wp->w_wrow - wp->w_view_height + 1; 1015 wp->w_skipcol += extra * width2; 1016 wp->w_wrow -= extra; 1017 } 1018 1019 // extra could be either positive or negative 1020 extra = (prev_skipcol - wp->w_skipcol) / width2; 1021 // TODO(bfredl): this is very suspicious when not called by win_update() 1022 // We should not randomly alter screen state outside of update_screen() :( 1023 if (wp->w_grid.target) { 1024 win_scroll_lines(wp, 0, extra); 1025 } 1026 } else if (!wp->w_p_sms) { 1027 wp->w_skipcol = 0; 1028 } 1029 if (prev_skipcol != wp->w_skipcol) { 1030 redraw_later(wp, UPD_SOME_VALID); 1031 } 1032 1033 redraw_for_cursorcolumn(wp); 1034 1035 // now w_leftcol and w_skipcol are valid, avoid check_cursor_moved() 1036 // thinking otherwise 1037 wp->w_valid_leftcol = wp->w_leftcol; 1038 wp->w_valid_skipcol = wp->w_skipcol; 1039 1040 wp->w_valid |= VALID_WCOL|VALID_WROW|VALID_VIRTCOL; 1041 } 1042 1043 /// Compute the screen position of text character at "pos" in window "wp" 1044 /// The resulting values are one-based, zero when character is not visible. 1045 /// 1046 /// @param[out] rowp screen row 1047 /// @param[out] scolp start screen column 1048 /// @param[out] ccolp cursor screen column 1049 /// @param[out] ecolp end screen column 1050 void textpos2screenpos(win_T *wp, pos_T *pos, int *rowp, int *scolp, int *ccolp, int *ecolp, 1051 bool local) 1052 { 1053 colnr_T scol = 0; 1054 colnr_T ccol = 0; 1055 colnr_T ecol = 0; 1056 int row = 0; 1057 colnr_T coloff = 0; 1058 bool visible_row = false; 1059 bool is_folded = false; 1060 1061 linenr_T lnum = pos->lnum; 1062 if (lnum >= wp->w_topline && lnum <= wp->w_botline) { 1063 is_folded = hasFolding(wp, lnum, &lnum, NULL); 1064 row = plines_m_win(wp, wp->w_topline, lnum - 1, INT_MAX); 1065 // "row" should be the screen line where line "lnum" begins, which can 1066 // be negative if "lnum" is "w_topline" and "w_skipcol" is non-zero. 1067 row -= adjust_plines_for_skipcol(wp); 1068 // Add filler lines above this buffer line. 1069 row += lnum == wp->w_topline ? wp->w_topfill : win_get_fill(wp, lnum); 1070 visible_row = true; 1071 } else if (!local || lnum < wp->w_topline) { 1072 row = 0; 1073 } else { 1074 row = wp->w_view_height - 1; 1075 } 1076 1077 bool existing_row = (lnum > 0 && lnum <= wp->w_buffer->b_ml.ml_line_count); 1078 1079 if ((local || visible_row) && existing_row) { 1080 const colnr_T off = win_col_off(wp); 1081 if (is_folded) { 1082 row += (local ? 0 : wp->w_winrow + wp->w_winrow_off) + 1; 1083 coloff = (local ? 0 : wp->w_wincol + wp->w_wincol_off) + 1 + off; 1084 } else { 1085 assert(lnum == pos->lnum); 1086 getvcol(wp, pos, &scol, &ccol, &ecol); 1087 1088 // similar to what is done in validate_cursor_col() 1089 colnr_T col = scol; 1090 col += off; 1091 int width = wp->w_view_width - off + win_col_off2(wp); 1092 1093 // long line wrapping, adjust row 1094 if (wp->w_p_wrap && col >= (colnr_T)wp->w_view_width && width > 0) { 1095 // use same formula as what is used in curs_columns() 1096 int rowoff = visible_row ? ((col - wp->w_view_width) / width + 1) : 0; 1097 col -= rowoff * width; 1098 row += rowoff; 1099 } 1100 1101 col -= wp->w_leftcol; 1102 1103 if (col >= 0 && col < wp->w_view_width && row >= 0 && row < wp->w_view_height) { 1104 coloff = col - scol + (local ? 0 : wp->w_wincol + wp->w_wincol_off) + 1; 1105 row += (local ? 0 : wp->w_winrow + wp->w_winrow_off) + 1; 1106 } else { 1107 // character is left, right or below of the window 1108 scol = ccol = ecol = 0; 1109 if (local) { 1110 coloff = col < 0 ? -1 : wp->w_view_width + 1; 1111 } else { 1112 row = 0; 1113 } 1114 } 1115 } 1116 } 1117 *rowp = row; 1118 *scolp = scol + coloff; 1119 *ccolp = ccol + coloff; 1120 *ecolp = ecol + coloff; 1121 } 1122 1123 /// "screenpos({winid}, {lnum}, {col})" function 1124 void f_screenpos(typval_T *argvars, typval_T *rettv, EvalFuncData fptr) 1125 { 1126 tv_dict_alloc_ret(rettv); 1127 dict_T *dict = rettv->vval.v_dict; 1128 1129 win_T *wp = find_win_by_nr_or_id(&argvars[0]); 1130 if (wp == NULL) { 1131 return; 1132 } 1133 1134 pos_T pos = { 1135 .lnum = (linenr_T)tv_get_number(&argvars[1]), 1136 .col = (colnr_T)tv_get_number(&argvars[2]) - 1, 1137 .coladd = 0 1138 }; 1139 if (pos.lnum > wp->w_buffer->b_ml.ml_line_count) { 1140 semsg(_(e_invalid_line_number_nr), pos.lnum); 1141 return; 1142 } 1143 pos.col = MAX(pos.col, 0); 1144 int row = 0; 1145 int scol = 0; 1146 int ccol = 0; 1147 int ecol = 0; 1148 textpos2screenpos(wp, &pos, &row, &scol, &ccol, &ecol, false); 1149 1150 tv_dict_add_nr(dict, S_LEN("row"), row); 1151 tv_dict_add_nr(dict, S_LEN("col"), scol); 1152 tv_dict_add_nr(dict, S_LEN("curscol"), ccol); 1153 tv_dict_add_nr(dict, S_LEN("endcol"), ecol); 1154 } 1155 1156 /// Convert a virtual (screen) column to a character column. The first column 1157 /// is one. For a multibyte character, the column number of the first byte is 1158 /// returned. 1159 static int virtcol2col(win_T *wp, linenr_T lnum, int vcol) 1160 { 1161 int offset = vcol2col(wp, lnum, vcol - 1, NULL); 1162 char *line = ml_get_buf(wp->w_buffer, lnum); 1163 char *p = line + offset; 1164 1165 if (*p == NUL) { 1166 if (p == line) { // empty line 1167 return 0; 1168 } 1169 // Move to the first byte of the last char. 1170 MB_PTR_BACK(line, p); 1171 } 1172 return (int)(p - line + 1); 1173 } 1174 1175 /// "virtcol2col({winid}, {lnum}, {col})" function 1176 void f_virtcol2col(typval_T *argvars, typval_T *rettv, EvalFuncData fptr) 1177 { 1178 rettv->vval.v_number = -1; 1179 1180 if (tv_check_for_number_arg(argvars, 0) == FAIL 1181 || tv_check_for_number_arg(argvars, 1) == FAIL 1182 || tv_check_for_number_arg(argvars, 2) == FAIL) { 1183 return; 1184 } 1185 1186 win_T *wp = find_win_by_nr_or_id(&argvars[0]); 1187 if (wp == NULL) { 1188 return; 1189 } 1190 1191 bool error = false; 1192 linenr_T lnum = (linenr_T)tv_get_number_chk(&argvars[1], &error); 1193 if (error || lnum < 0 || lnum > wp->w_buffer->b_ml.ml_line_count) { 1194 return; 1195 } 1196 1197 int screencol = (int)tv_get_number_chk(&argvars[2], &error); 1198 if (error || screencol < 0) { 1199 return; 1200 } 1201 1202 rettv->vval.v_number = virtcol2col(wp, lnum, screencol); 1203 } 1204 1205 /// Make sure the cursor is in the visible part of the topline after scrolling 1206 /// the screen with 'smoothscroll'. 1207 static void cursor_correct_sms(win_T *wp) 1208 { 1209 if (!wp->w_p_sms || !wp->w_p_wrap || wp->w_cursor.lnum != wp->w_topline) { 1210 return; 1211 } 1212 1213 int so = get_scrolloff_value(wp); 1214 int width1 = wp->w_view_width - win_col_off(wp); 1215 int width2 = width1 + win_col_off2(wp); 1216 int so_cols = so == 0 ? 0 : width1 + (so - 1) * width2; 1217 int space_cols = (wp->w_view_height - 1) * width2; 1218 int size = so == 0 ? 0 : linetabsize_eol(wp, wp->w_topline); 1219 1220 if (wp->w_topline == 1 && wp->w_skipcol == 0) { 1221 so_cols = 0; // Ignore 'scrolloff' at top of buffer. 1222 } else if (so_cols > space_cols / 2) { 1223 so_cols = space_cols / 2; // Not enough room: put cursor in the middle. 1224 } 1225 1226 // Not enough screen lines in topline: ignore 'scrolloff'. 1227 while (so_cols > size && so_cols - width2 >= width1 && width1 > 0) { 1228 so_cols -= width2; 1229 } 1230 if (so_cols >= width1 && so_cols > size) { 1231 so_cols -= width1; 1232 } 1233 1234 int overlap = wp->w_skipcol == 0 1235 ? 0 : sms_marker_overlap(wp, wp->w_view_width - width2); 1236 // If we have non-zero scrolloff, ignore marker overlap. 1237 int top = wp->w_skipcol + (so_cols != 0 ? so_cols : overlap); 1238 int bot = wp->w_skipcol + width1 + (wp->w_view_height - 1) * width2 - so_cols; 1239 1240 validate_virtcol(wp); 1241 colnr_T col = wp->w_virtcol; 1242 1243 if (col < top) { 1244 if (col < width1) { 1245 col += width1; 1246 } 1247 while (width2 > 0 && col < top) { 1248 col += width2; 1249 } 1250 } else { 1251 while (width2 > 0 && col >= bot) { 1252 col -= width2; 1253 } 1254 } 1255 1256 if (col != wp->w_virtcol) { 1257 wp->w_curswant = col; 1258 int rc = coladvance(wp, wp->w_curswant); 1259 // validate_virtcol() marked various things as valid, but after 1260 // moving the cursor they need to be recomputed 1261 wp->w_valid &= ~(VALID_WROW|VALID_WCOL|VALID_CHEIGHT|VALID_CROW|VALID_VIRTCOL); 1262 if (rc == FAIL && wp->w_skipcol > 0 1263 && wp->w_cursor.lnum < wp->w_buffer->b_ml.ml_line_count) { 1264 validate_virtcol(wp); 1265 if (wp->w_virtcol < wp->w_skipcol + overlap) { 1266 // Cursor still not visible: move it to the next line instead. 1267 wp->w_cursor.lnum++; 1268 wp->w_cursor.col = 0; 1269 wp->w_cursor.coladd = 0; 1270 wp->w_curswant = 0; 1271 wp->w_valid &= ~VALID_VIRTCOL; 1272 } 1273 } 1274 } 1275 } 1276 1277 /// Scroll "count" lines up or down, and redraw. 1278 void scroll_redraw(int up, linenr_T count) 1279 { 1280 linenr_T prev_topline = curwin->w_topline; 1281 int prev_skipcol = curwin->w_skipcol; 1282 int prev_topfill = curwin->w_topfill; 1283 linenr_T prev_lnum = curwin->w_cursor.lnum; 1284 1285 bool moved = up 1286 ? scrollup(curwin, count, true) 1287 : scrolldown(curwin, count, true); 1288 1289 if (get_scrolloff_value(curwin) > 0) { 1290 // Adjust the cursor position for 'scrolloff'. Mark w_topline as 1291 // valid, otherwise the screen jumps back at the end of the file. 1292 cursor_correct(curwin); 1293 check_cursor_moved(curwin); 1294 curwin->w_valid |= VALID_TOPLINE; 1295 1296 // If moved back to where we were, at least move the cursor, otherwise 1297 // we get stuck at one position. Don't move the cursor up if the 1298 // first line of the buffer is already on the screen 1299 while (curwin->w_topline == prev_topline 1300 && curwin->w_skipcol == prev_skipcol 1301 && curwin->w_topfill == prev_topfill) { 1302 if (up) { 1303 if (curwin->w_cursor.lnum > prev_lnum 1304 || cursor_down(1L, false) == FAIL) { 1305 break; 1306 } 1307 } else { 1308 if (curwin->w_cursor.lnum < prev_lnum 1309 || prev_topline == 1L 1310 || cursor_up(1L, false) == FAIL) { 1311 break; 1312 } 1313 } 1314 // Mark w_topline as valid, otherwise the screen jumps back at the 1315 // end of the file. 1316 check_cursor_moved(curwin); 1317 curwin->w_valid |= VALID_TOPLINE; 1318 } 1319 } 1320 1321 if (moved) { 1322 curwin->w_viewport_invalid = true; 1323 } 1324 1325 cursor_correct_sms(curwin); 1326 if (curwin->w_cursor.lnum != prev_lnum) { 1327 coladvance(curwin, curwin->w_curswant); 1328 } 1329 redraw_later(curwin, UPD_VALID); 1330 } 1331 1332 /// Scroll a window down by "line_count" logical lines. "CTRL-Y" 1333 /// 1334 /// @param line_count number of lines to scroll 1335 /// @param byfold if true, count a closed fold as one line 1336 bool scrolldown(win_T *wp, linenr_T line_count, int byfold) 1337 { 1338 int done = 0; // total # of physical lines done 1339 int width1 = 0; 1340 int width2 = 0; 1341 bool do_sms = wp->w_p_wrap && wp->w_p_sms; 1342 1343 if (do_sms) { 1344 width1 = wp->w_view_width - win_col_off(wp); 1345 width2 = width1 + win_col_off2(wp); 1346 } 1347 1348 // Make sure w_topline is at the first of a sequence of folded lines. 1349 hasFolding(wp, wp->w_topline, &wp->w_topline, NULL); 1350 validate_cursor(wp); // w_wrow needs to be valid 1351 for (int todo = line_count; todo > 0; todo--) { 1352 bool can_fill = wp->w_topfill < wp->w_view_height - 1 1353 && wp->w_topfill < win_get_fill(wp, wp->w_topline); 1354 // break when at the very top 1355 if (wp->w_topline == 1 && !can_fill && (!do_sms || wp->w_skipcol < width1)) { 1356 break; 1357 } 1358 if (do_sms && wp->w_skipcol >= width1) { 1359 // scroll a screen line down 1360 if (wp->w_skipcol >= width1 + width2) { 1361 wp->w_skipcol -= width2; 1362 } else { 1363 wp->w_skipcol -= width1; 1364 } 1365 redraw_later(wp, UPD_NOT_VALID); 1366 done++; 1367 } else if (can_fill) { 1368 wp->w_topfill++; 1369 done++; 1370 } else { 1371 // scroll a text line down 1372 wp->w_topline--; 1373 wp->w_skipcol = 0; 1374 wp->w_topfill = 0; 1375 // A sequence of folded lines only counts for one logical line 1376 linenr_T first; 1377 if (hasFolding(wp, wp->w_topline, &first, NULL)) { 1378 done += !decor_conceal_line(wp, first - 1, false); 1379 if (!byfold) { 1380 todo -= wp->w_topline - first - 1; 1381 } 1382 wp->w_botline -= wp->w_topline - first; 1383 wp->w_topline = first; 1384 } else if (decor_conceal_line(wp, wp->w_topline - 1, false)) { 1385 todo++; 1386 } else { 1387 if (do_sms) { 1388 int size = linetabsize_eol(wp, wp->w_topline); 1389 if (size > width1) { 1390 wp->w_skipcol = width1; 1391 size -= width1; 1392 redraw_later(wp, UPD_NOT_VALID); 1393 } 1394 while (size > width2) { 1395 wp->w_skipcol += width2; 1396 size -= width2; 1397 } 1398 done++; 1399 } else { 1400 done += plines_win_nofill(wp, wp->w_topline, true); 1401 } 1402 } 1403 } 1404 wp->w_botline--; // approximate w_botline 1405 invalidate_botline_win(wp); 1406 } 1407 1408 // Adjust for concealed lines above w_topline 1409 while (wp->w_topline > 1 && decor_conceal_line(wp, wp->w_topline - 2, false)) { 1410 wp->w_topline--; 1411 hasFolding(wp, wp->w_topline, &wp->w_topline, NULL); 1412 } 1413 1414 wp->w_wrow += done; // keep w_wrow updated 1415 wp->w_cline_row += done; // keep w_cline_row updated 1416 1417 if (wp->w_cursor.lnum == wp->w_topline) { 1418 wp->w_cline_row = 0; 1419 } 1420 check_topfill(wp, true); 1421 1422 // Compute the row number of the last row of the cursor line 1423 // and move the cursor onto the displayed part of the window. 1424 int wrow = wp->w_wrow; 1425 if (wp->w_p_wrap && wp->w_view_width != 0) { 1426 validate_virtcol(wp); 1427 validate_cheight(wp); 1428 wrow += wp->w_cline_height - 1 - 1429 wp->w_virtcol / wp->w_view_width; 1430 } 1431 bool moved = false; 1432 while (wrow >= wp->w_view_height && wp->w_cursor.lnum > 1) { 1433 linenr_T first; 1434 if (hasFolding(wp, wp->w_cursor.lnum, &first, NULL)) { 1435 wrow -= !decor_conceal_line(wp, wp->w_cursor.lnum - 1, false); 1436 wp->w_cursor.lnum = MAX(first - 1, 1); 1437 } else { 1438 wrow -= plines_win(wp, wp->w_cursor.lnum--, true); 1439 } 1440 wp->w_valid &= 1441 ~(VALID_WROW|VALID_WCOL|VALID_CHEIGHT|VALID_CROW|VALID_VIRTCOL); 1442 moved = true; 1443 } 1444 if (moved) { 1445 // Move cursor to first line of closed fold. 1446 foldAdjustCursor(wp); 1447 coladvance(wp, wp->w_curswant); 1448 } 1449 wp->w_cursor.lnum = MAX(wp->w_cursor.lnum, wp->w_topline); 1450 1451 return moved; 1452 } 1453 1454 /// Scroll a window up by "line_count" logical lines. "CTRL-E" 1455 /// 1456 /// @param line_count number of lines to scroll 1457 /// @param byfold if true, count a closed fold as one line 1458 bool scrollup(win_T *wp, linenr_T line_count, bool byfold) 1459 { 1460 linenr_T topline = wp->w_topline; 1461 linenr_T botline = wp->w_botline; 1462 bool do_sms = wp->w_p_wrap && wp->w_p_sms; 1463 1464 if (do_sms || (byfold && win_lines_concealed(wp)) || win_may_fill(wp)) { 1465 int width1 = wp->w_view_width - win_col_off(wp); 1466 int width2 = width1 + win_col_off2(wp); 1467 int size = 0; 1468 const colnr_T prev_skipcol = wp->w_skipcol; 1469 1470 if (do_sms) { 1471 size = linetabsize_eol(wp, wp->w_topline); 1472 } 1473 1474 // diff mode: first consume "topfill" 1475 // 'smoothscroll': increase "w_skipcol" until it goes over the end of 1476 // the line, then advance to the next line. 1477 // folding: count each sequence of folded lines as one logical line. 1478 for (int todo = line_count; todo > 0; todo--) { 1479 todo += decor_conceal_line(wp, wp->w_topline - 1, false); 1480 if (wp->w_topfill > 0) { 1481 wp->w_topfill--; 1482 } else { 1483 linenr_T lnum = wp->w_topline; 1484 if (byfold) { 1485 // for a closed fold: go to the last line in the fold 1486 hasFolding(wp, lnum, NULL, &lnum); 1487 } 1488 if (lnum == wp->w_topline && do_sms) { 1489 // 'smoothscroll': increase "w_skipcol" until it goes over 1490 // the end of the line, then advance to the next line. 1491 int add = wp->w_skipcol > 0 ? width2 : width1; 1492 wp->w_skipcol += add; 1493 if (wp->w_skipcol >= size) { 1494 if (lnum == wp->w_buffer->b_ml.ml_line_count) { 1495 // at the last screen line, can't scroll further 1496 wp->w_skipcol -= add; 1497 break; 1498 } 1499 lnum++; 1500 } 1501 } else { 1502 if (lnum >= wp->w_buffer->b_ml.ml_line_count) { 1503 break; 1504 } 1505 lnum++; 1506 } 1507 1508 if (lnum > wp->w_topline) { 1509 // approximate w_botline 1510 wp->w_botline += lnum - wp->w_topline; 1511 wp->w_topline = lnum; 1512 wp->w_topfill = win_get_fill(wp, lnum); 1513 wp->w_skipcol = 0; 1514 if (todo > 1 && do_sms) { 1515 size = linetabsize_eol(wp, wp->w_topline); 1516 } 1517 } 1518 } 1519 } 1520 1521 if (prev_skipcol > 0 || wp->w_skipcol > 0) { 1522 // need to redraw more, because wl_size of the (new) topline may 1523 // now be invalid 1524 redraw_later(wp, UPD_NOT_VALID); 1525 } 1526 } else { 1527 wp->w_topline += line_count; 1528 wp->w_botline += line_count; // approximate w_botline 1529 } 1530 1531 wp->w_topline = MIN(wp->w_topline, wp->w_buffer->b_ml.ml_line_count); 1532 wp->w_botline = MIN(wp->w_botline, wp->w_buffer->b_ml.ml_line_count + 1); 1533 1534 check_topfill(wp, false); 1535 1536 // Make sure w_topline is at the first of a sequence of folded lines. 1537 hasFolding(wp, wp->w_topline, &wp->w_topline, NULL); 1538 1539 wp->w_valid &= ~(VALID_WROW|VALID_CROW|VALID_BOTLINE); 1540 if (wp->w_cursor.lnum < wp->w_topline) { 1541 wp->w_cursor.lnum = wp->w_topline; 1542 wp->w_valid &= 1543 ~(VALID_WROW|VALID_WCOL|VALID_CHEIGHT|VALID_CROW|VALID_VIRTCOL); 1544 coladvance(wp, wp->w_curswant); 1545 } 1546 1547 bool moved = topline != wp->w_topline || botline != wp->w_botline; 1548 1549 return moved; 1550 } 1551 1552 /// Called after changing the cursor column: make sure that curwin->w_skipcol is 1553 /// valid for 'smoothscroll'. 1554 void adjust_skipcol(void) 1555 { 1556 if (!curwin->w_p_wrap || !curwin->w_p_sms || curwin->w_cursor.lnum != curwin->w_topline) { 1557 return; 1558 } 1559 1560 int width1 = curwin->w_view_width - win_col_off(curwin); 1561 if (width1 <= 0) { 1562 return; // no text will be displayed 1563 } 1564 int width2 = width1 + win_col_off2(curwin); 1565 int so = get_scrolloff_value(curwin); 1566 colnr_T scrolloff_cols = so == 0 ? 0 : width1 + (so - 1) * width2; 1567 bool scrolled = false; 1568 1569 validate_cheight(curwin); 1570 if (curwin->w_cline_height == curwin->w_view_height 1571 // w_cline_height may be capped at w_view_height, check there aren't 1572 // actually more lines. 1573 && plines_win(curwin, curwin->w_cursor.lnum, false) <= curwin->w_view_height) { 1574 // the line just fits in the window, don't scroll 1575 reset_skipcol(curwin); 1576 return; 1577 } 1578 1579 validate_virtcol(curwin); 1580 int overlap = sms_marker_overlap(curwin, curwin->w_view_width - width2); 1581 while (curwin->w_skipcol > 0 1582 && curwin->w_virtcol < curwin->w_skipcol + overlap + scrolloff_cols) { 1583 // scroll a screen line down 1584 if (curwin->w_skipcol >= width1 + width2) { 1585 curwin->w_skipcol -= width2; 1586 } else { 1587 curwin->w_skipcol -= width1; 1588 } 1589 scrolled = true; 1590 } 1591 if (scrolled) { 1592 validate_virtcol(curwin); 1593 redraw_later(curwin, UPD_NOT_VALID); 1594 return; // don't scroll in the other direction now 1595 } 1596 int row = 0; 1597 colnr_T col = curwin->w_virtcol + scrolloff_cols; 1598 1599 // Avoid adjusting for 'scrolloff' beyond the text line height. 1600 if (scrolloff_cols > 0) { 1601 int size = linetabsize_eol(curwin, curwin->w_topline); 1602 size = width1 + width2 * ((size - width1 + width2 - 1) / width2); 1603 while (col > size) { 1604 col -= width2; 1605 } 1606 } 1607 col -= curwin->w_skipcol; 1608 1609 if (col >= width1) { 1610 col -= width1; 1611 row++; 1612 } 1613 if (col > width2) { 1614 row += (int)col / width2; 1615 } 1616 if (row >= curwin->w_view_height) { 1617 if (curwin->w_skipcol == 0) { 1618 curwin->w_skipcol += width1; 1619 row--; 1620 } 1621 if (row >= curwin->w_view_height) { 1622 curwin->w_skipcol += (row - curwin->w_view_height) * width2; 1623 } 1624 redraw_later(curwin, UPD_NOT_VALID); 1625 } 1626 } 1627 1628 /// Don't end up with too many filler lines in the window. 1629 /// 1630 /// @param down when true scroll down when not enough space 1631 void check_topfill(win_T *wp, bool down) 1632 { 1633 if (wp->w_topfill > 0) { 1634 int n = plines_win_nofill(wp, wp->w_topline, true); 1635 if (wp->w_topfill + n > wp->w_view_height) { 1636 if (down && wp->w_topline > 1) { 1637 wp->w_topline--; 1638 wp->w_topfill = 0; 1639 } else { 1640 wp->w_topfill = wp->w_view_height - n; 1641 wp->w_topfill = MAX(wp->w_topfill, 0); 1642 } 1643 } 1644 } 1645 win_check_anchored_floats(wp); 1646 } 1647 1648 // Scroll the screen one line down, but don't do it if it would move the 1649 // cursor off the screen. 1650 void scrolldown_clamp(void) 1651 { 1652 bool can_fill = (curwin->w_topfill < win_get_fill(curwin, curwin->w_topline)); 1653 1654 if (curwin->w_topline <= 1 1655 && !can_fill) { 1656 return; 1657 } 1658 1659 validate_cursor(curwin); // w_wrow needs to be valid 1660 1661 // Compute the row number of the last row of the cursor line 1662 // and make sure it doesn't go off the screen. Make sure the cursor 1663 // doesn't go past 'scrolloff' lines from the screen end. 1664 int end_row = curwin->w_wrow; 1665 if (can_fill) { 1666 end_row++; 1667 } else { 1668 end_row += plines_win_nofill(curwin, curwin->w_topline - 1, true); 1669 } 1670 if (curwin->w_p_wrap && curwin->w_view_width != 0) { 1671 validate_cheight(curwin); 1672 validate_virtcol(curwin); 1673 end_row += curwin->w_cline_height - 1 - 1674 curwin->w_virtcol / curwin->w_view_width; 1675 } 1676 if (end_row < curwin->w_view_height - get_scrolloff_value(curwin)) { 1677 if (can_fill) { 1678 curwin->w_topfill++; 1679 check_topfill(curwin, true); 1680 } else { 1681 curwin->w_topline--; 1682 curwin->w_topfill = 0; 1683 } 1684 hasFolding(curwin, curwin->w_topline, &curwin->w_topline, NULL); 1685 curwin->w_botline--; // approximate w_botline 1686 curwin->w_valid &= ~(VALID_WROW|VALID_CROW|VALID_BOTLINE); 1687 } 1688 } 1689 1690 // Scroll the screen one line up, but don't do it if it would move the cursor 1691 // off the screen. 1692 void scrollup_clamp(void) 1693 { 1694 if (curwin->w_topline == curbuf->b_ml.ml_line_count 1695 && curwin->w_topfill == 0) { 1696 return; 1697 } 1698 1699 validate_cursor(curwin); // w_wrow needs to be valid 1700 1701 // Compute the row number of the first row of the cursor line 1702 // and make sure it doesn't go off the screen. Make sure the cursor 1703 // doesn't go before 'scrolloff' lines from the screen start. 1704 int start_row = (curwin->w_wrow 1705 - plines_win_nofill(curwin, curwin->w_topline, true) 1706 - curwin->w_topfill); 1707 if (curwin->w_p_wrap && curwin->w_view_width != 0) { 1708 validate_virtcol(curwin); 1709 start_row -= curwin->w_virtcol / curwin->w_view_width; 1710 } 1711 if (start_row >= get_scrolloff_value(curwin)) { 1712 if (curwin->w_topfill > 0) { 1713 curwin->w_topfill--; 1714 } else { 1715 hasFolding(curwin, curwin->w_topline, NULL, &curwin->w_topline); 1716 curwin->w_topline++; 1717 } 1718 curwin->w_botline++; // approximate w_botline 1719 curwin->w_valid &= ~(VALID_WROW|VALID_CROW|VALID_BOTLINE); 1720 } 1721 } 1722 1723 // Add one line above "lp->lnum". This can be a filler line, a closed fold or 1724 // a (wrapped) text line. Uses and sets "lp->fill". 1725 // Returns the height of the added line in "lp->height". 1726 // Lines above the first one are incredibly high: MAXCOL. 1727 // When "winheight" is true limit to window height. 1728 static void topline_back_winheight(win_T *wp, lineoff_T *lp, int winheight) 1729 { 1730 if (lp->fill < win_get_fill(wp, lp->lnum)) { 1731 // Add a filler line 1732 lp->fill++; 1733 lp->height = 1; 1734 } else { 1735 lp->lnum--; 1736 lp->fill = 0; 1737 if (lp->lnum < 1) { 1738 lp->height = MAXCOL; 1739 } else if (hasFolding(wp, lp->lnum, &lp->lnum, NULL)) { 1740 // Add a closed fold unless concealed. 1741 lp->height = !decor_conceal_line(wp, lp->lnum - 1, false); 1742 } else { 1743 lp->height = plines_win_nofill(wp, lp->lnum, winheight); 1744 } 1745 } 1746 } 1747 1748 static void topline_back(win_T *wp, lineoff_T *lp) 1749 { 1750 topline_back_winheight(wp, lp, true); 1751 } 1752 1753 // Add one line below "lp->lnum". This can be a filler line, a closed fold or 1754 // a (wrapped) text line. Uses and sets "lp->fill". 1755 // Returns the height of the added line in "lp->height". 1756 // Lines below the last one are incredibly high. 1757 static void botline_forw(win_T *wp, lineoff_T *lp) 1758 { 1759 if (lp->fill < win_get_fill(wp, lp->lnum + 1)) { 1760 // Add a filler line. 1761 lp->fill++; 1762 lp->height = 1; 1763 } else { 1764 lp->lnum++; 1765 lp->fill = 0; 1766 assert(wp->w_buffer != 0); 1767 if (lp->lnum > wp->w_buffer->b_ml.ml_line_count) { 1768 lp->height = MAXCOL; 1769 } else if (hasFolding(wp, lp->lnum, NULL, &lp->lnum)) { 1770 // Add a closed fold unless concealed. 1771 lp->height = !decor_conceal_line(wp, lp->lnum - 1, false); 1772 } else { 1773 lp->height = plines_win_nofill(wp, lp->lnum, true); 1774 } 1775 } 1776 } 1777 1778 // Recompute topline to put the cursor at the top of the window. 1779 // Scroll at least "min_scroll" lines. 1780 // If "always" is true, always set topline (for "zt"). 1781 void scroll_cursor_top(win_T *wp, int min_scroll, int always) 1782 { 1783 linenr_T old_topline = wp->w_topline; 1784 int old_skipcol = wp->w_skipcol; 1785 linenr_T old_topfill = wp->w_topfill; 1786 int off = get_scrolloff_value(wp); 1787 1788 if (mouse_dragging > 0) { 1789 off = mouse_dragging - 1; 1790 } 1791 1792 // Decrease topline until: 1793 // - it has become 1 1794 // - (part of) the cursor line is moved off the screen or 1795 // - moved at least 'scrolljump' lines and 1796 // - at least 'scrolloff' lines above and below the cursor 1797 validate_cheight(wp); 1798 int scrolled = 0; 1799 int used = wp->w_cline_height; // includes filler lines above 1800 if (wp->w_cursor.lnum < wp->w_topline) { 1801 scrolled = used; 1802 } 1803 1804 linenr_T top; // just above displayed lines 1805 linenr_T bot; // just below displayed lines 1806 if (hasFolding(wp, wp->w_cursor.lnum, &top, &bot)) { 1807 top--; 1808 bot++; 1809 } else { 1810 top = wp->w_cursor.lnum - 1; 1811 bot = wp->w_cursor.lnum + 1; 1812 } 1813 linenr_T new_topline = top + 1; 1814 1815 // "used" already contains the number of filler lines above, don't add it 1816 // again. 1817 // Hide filler lines above cursor line by adding them to "extra". 1818 int extra = win_get_fill(wp, wp->w_cursor.lnum); 1819 1820 // Check if the lines from "top" to "bot" fit in the window. If they do, 1821 // set new_topline and advance "top" and "bot" to include more lines. 1822 while (top > 0) { 1823 int i = plines_win_nofill(wp, top, true); 1824 hasFolding(wp, top, &top, NULL); 1825 if (top < wp->w_topline) { 1826 scrolled += i; 1827 } 1828 1829 // If scrolling is needed, scroll at least 'sj' lines. 1830 if ((new_topline >= wp->w_topline || scrolled > min_scroll) && extra >= off) { 1831 break; 1832 } 1833 1834 used += i; 1835 if (extra + i <= off && bot < wp->w_buffer->b_ml.ml_line_count) { 1836 used += plines_win_full(wp, bot, &bot, NULL, true, true); 1837 } 1838 if (used > wp->w_view_height) { 1839 break; 1840 } 1841 1842 extra += i; 1843 new_topline = top; 1844 top--; 1845 bot++; 1846 } 1847 1848 // If we don't have enough space, put cursor in the middle. 1849 // This makes sure we get the same position when using "k" and "j" 1850 // in a small window. 1851 if (used > wp->w_view_height) { 1852 scroll_cursor_halfway(wp, false, false); 1853 } else { 1854 // If "always" is false, only adjust topline to a lower value, higher 1855 // value may happen with wrapping lines. 1856 if (new_topline < wp->w_topline || always) { 1857 wp->w_topline = new_topline; 1858 } 1859 wp->w_topline = MIN(wp->w_topline, wp->w_cursor.lnum); 1860 wp->w_topfill = win_get_fill(wp, wp->w_topline); 1861 if (wp->w_topfill > 0 && extra > off) { 1862 wp->w_topfill -= extra - off; 1863 wp->w_topfill = MAX(wp->w_topfill, 0); 1864 } 1865 check_topfill(wp, false); 1866 if (wp->w_topline != old_topline) { 1867 reset_skipcol(wp); 1868 } else if (wp->w_topline == wp->w_cursor.lnum) { 1869 validate_virtcol(wp); 1870 if (wp->w_skipcol >= wp->w_virtcol) { 1871 // TODO(vim): if the line doesn't fit may optimize w_skipcol instead 1872 // of making it zero 1873 reset_skipcol(wp); 1874 } 1875 } 1876 if (wp->w_topline != old_topline 1877 || wp->w_skipcol != old_skipcol 1878 || wp->w_topfill != old_topfill) { 1879 wp->w_valid &= 1880 ~(VALID_WROW|VALID_CROW|VALID_BOTLINE|VALID_BOTLINE_AP); 1881 } 1882 wp->w_valid |= VALID_TOPLINE; 1883 wp->w_viewport_invalid = true; 1884 } 1885 } 1886 1887 // Set w_empty_rows and w_filler_rows for window "wp", having used up "used" 1888 // screen lines for text lines. 1889 void set_empty_rows(win_T *wp, int used) 1890 { 1891 wp->w_filler_rows = 0; 1892 if (used == 0) { 1893 wp->w_empty_rows = 0; // single line that doesn't fit 1894 } else { 1895 wp->w_empty_rows = wp->w_view_height - used; 1896 if (wp->w_botline <= wp->w_buffer->b_ml.ml_line_count) { 1897 wp->w_filler_rows = win_get_fill(wp, wp->w_botline); 1898 if (wp->w_empty_rows > wp->w_filler_rows) { 1899 wp->w_empty_rows -= wp->w_filler_rows; 1900 } else { 1901 wp->w_filler_rows = wp->w_empty_rows; 1902 wp->w_empty_rows = 0; 1903 } 1904 } 1905 } 1906 } 1907 1908 /// Recompute topline to put the cursor at the bottom of the window. 1909 /// When scrolling scroll at least "min_scroll" lines. 1910 /// If "set_topbot" is true, set topline and botline first (for "zb"). 1911 /// This is messy stuff!!! 1912 void scroll_cursor_bot(win_T *wp, int min_scroll, bool set_topbot) 1913 { 1914 lineoff_T loff; 1915 linenr_T old_topline = wp->w_topline; 1916 int old_skipcol = wp->w_skipcol; 1917 int old_topfill = wp->w_topfill; 1918 linenr_T old_botline = wp->w_botline; 1919 int old_valid = wp->w_valid; 1920 int old_empty_rows = wp->w_empty_rows; 1921 linenr_T cln = wp->w_cursor.lnum; // Cursor Line Number 1922 bool do_sms = wp->w_p_wrap && wp->w_p_sms; 1923 1924 if (set_topbot) { 1925 int used = 0; 1926 wp->w_botline = cln + 1; 1927 loff.lnum = cln + 1; 1928 loff.fill = 0; 1929 while (true) { 1930 topline_back_winheight(wp, &loff, false); 1931 if (loff.height == MAXCOL) { 1932 break; 1933 } 1934 if (used + loff.height > wp->w_view_height) { 1935 if (do_sms) { 1936 // 'smoothscroll' and 'wrap' are set. The above line is 1937 // too long to show in its entirety, so we show just a part 1938 // of it. 1939 if (used < wp->w_view_height) { 1940 int plines_offset = used + loff.height - wp->w_view_height; 1941 used = wp->w_view_height; 1942 wp->w_topfill = loff.fill; 1943 wp->w_topline = loff.lnum; 1944 wp->w_skipcol = skipcol_from_plines(wp, plines_offset); 1945 } 1946 } 1947 break; 1948 } 1949 wp->w_topfill = loff.fill; 1950 wp->w_topline = loff.lnum; 1951 used += loff.height; 1952 } 1953 1954 set_empty_rows(wp, used); 1955 wp->w_valid |= VALID_BOTLINE|VALID_BOTLINE_AP; 1956 if (wp->w_topline != old_topline 1957 || wp->w_topfill != old_topfill 1958 || wp->w_skipcol != old_skipcol 1959 || wp->w_skipcol != 0) { 1960 wp->w_valid &= ~(VALID_WROW|VALID_CROW); 1961 if (wp->w_skipcol != old_skipcol) { 1962 redraw_later(wp, UPD_NOT_VALID); 1963 } else { 1964 reset_skipcol(wp); 1965 } 1966 } 1967 } else { 1968 validate_botline_win(wp); 1969 } 1970 1971 // The lines of the cursor line itself are always used. 1972 int used = plines_win_nofill(wp, cln, true); 1973 1974 int scrolled = 0; 1975 // If the cursor is on or below botline, we will at least scroll by the 1976 // height of the cursor line, which is "used". Correct for empty lines, 1977 // which are really part of botline. 1978 if (cln >= wp->w_botline) { 1979 scrolled = used; 1980 if (cln == wp->w_botline) { 1981 scrolled -= wp->w_empty_rows; 1982 } 1983 if (do_sms) { 1984 // 'smoothscroll' and 'wrap' are set. 1985 // Calculate how many screen lines the current top line of window 1986 // occupies. If it is occupying more than the entire window, we 1987 // need to scroll the additional clipped lines to scroll past the 1988 // top line before we can move on to the other lines. 1989 int top_plines = plines_win_nofill(wp, wp->w_topline, false); 1990 int width1 = wp->w_view_width - win_col_off(wp); 1991 1992 if (width1 > 0) { 1993 int width2 = width1 + win_col_off2(wp); 1994 int skip_lines = 0; 1995 1996 // A similar formula is used in curs_columns(). 1997 if (wp->w_skipcol > width1) { 1998 skip_lines += (wp->w_skipcol - width1) / width2 + 1; 1999 } else if (wp->w_skipcol > 0) { 2000 skip_lines = 1; 2001 } 2002 2003 top_plines -= skip_lines; 2004 if (top_plines > wp->w_view_height) { 2005 scrolled += (top_plines - wp->w_view_height); 2006 } 2007 } 2008 } 2009 } 2010 2011 lineoff_T boff; 2012 // Stop counting lines to scroll when 2013 // - hitting start of the file 2014 // - scrolled nothing or at least 'sj' lines 2015 // - at least 'so' lines below the cursor 2016 // - lines between botline and cursor have been counted 2017 if (!hasFolding(wp, wp->w_cursor.lnum, &loff.lnum, &boff.lnum)) { 2018 loff.lnum = cln; 2019 boff.lnum = cln; 2020 } 2021 loff.fill = 0; 2022 boff.fill = 0; 2023 int fill_below_window = win_get_fill(wp, wp->w_botline) - wp->w_filler_rows; 2024 2025 int extra = 0; 2026 int so = get_scrolloff_value(wp); 2027 while (loff.lnum > 1) { 2028 // Stop when scrolled nothing or at least "min_scroll", found "extra" 2029 // context for 'scrolloff' and counted all lines below the window. 2030 if ((((scrolled <= 0 || scrolled >= min_scroll) 2031 && extra >= (mouse_dragging > 0 ? mouse_dragging - 1 : so)) 2032 || boff.lnum + 1 > wp->w_buffer->b_ml.ml_line_count) 2033 && loff.lnum <= wp->w_botline 2034 && (loff.lnum < wp->w_botline 2035 || loff.fill >= fill_below_window)) { 2036 break; 2037 } 2038 2039 // Add one line above 2040 topline_back(wp, &loff); 2041 if (loff.height == MAXCOL) { 2042 used = MAXCOL; 2043 } else { 2044 used += loff.height; 2045 } 2046 if (used > wp->w_view_height) { 2047 break; 2048 } 2049 if (loff.lnum >= wp->w_botline 2050 && (loff.lnum > wp->w_botline 2051 || loff.fill <= fill_below_window)) { 2052 // Count screen lines that are below the window. 2053 scrolled += loff.height; 2054 if (loff.lnum == wp->w_botline 2055 && loff.fill == 0) { 2056 scrolled -= wp->w_empty_rows; 2057 } 2058 } 2059 2060 if (boff.lnum < wp->w_buffer->b_ml.ml_line_count) { 2061 // Add one line below 2062 botline_forw(wp, &boff); 2063 used += boff.height; 2064 if (used > wp->w_view_height) { 2065 break; 2066 } 2067 if (extra < (mouse_dragging > 0 ? mouse_dragging - 1 : so) 2068 || scrolled < min_scroll) { 2069 extra += boff.height; 2070 if (boff.lnum >= wp->w_botline 2071 || (boff.lnum + 1 == wp->w_botline 2072 && boff.fill > wp->w_filler_rows)) { 2073 // Count screen lines that are below the window. 2074 scrolled += boff.height; 2075 if (boff.lnum == wp->w_botline 2076 && boff.fill == 0) { 2077 scrolled -= wp->w_empty_rows; 2078 } 2079 } 2080 } 2081 } 2082 } 2083 2084 linenr_T line_count; 2085 // wp->w_empty_rows is larger, no need to scroll 2086 if (scrolled <= 0) { 2087 line_count = 0; 2088 // more than a screenfull, don't scroll but redraw 2089 } else if (used > wp->w_view_height) { 2090 line_count = used; 2091 // scroll minimal number of lines 2092 } else { 2093 line_count = 0; 2094 boff.fill = wp->w_topfill; 2095 boff.lnum = wp->w_topline - 1; 2096 int i; 2097 for (i = 0; i < scrolled && boff.lnum < wp->w_botline;) { 2098 botline_forw(wp, &boff); 2099 i += boff.height; 2100 line_count++; 2101 } 2102 if (i < scrolled) { // below wp->w_botline, don't scroll 2103 line_count = 9999; 2104 } 2105 } 2106 2107 // Scroll up if the cursor is off the bottom of the screen a bit. 2108 // Otherwise put it at 1/2 of the screen. 2109 if (line_count >= wp->w_view_height && line_count > min_scroll) { 2110 scroll_cursor_halfway(wp, false, true); 2111 } else if (line_count > 0) { 2112 if (do_sms) { 2113 scrollup(wp, scrolled, true); // TODO(vim): 2114 } else { 2115 scrollup(wp, line_count, true); 2116 } 2117 } 2118 2119 // If topline didn't change we need to restore w_botline and w_empty_rows 2120 // (we changed them). 2121 // If topline did change, update_screen() will set botline. 2122 if (wp->w_topline == old_topline && wp->w_skipcol == old_skipcol && set_topbot) { 2123 wp->w_botline = old_botline; 2124 wp->w_empty_rows = old_empty_rows; 2125 wp->w_valid = old_valid; 2126 } 2127 wp->w_valid |= VALID_TOPLINE; 2128 wp->w_viewport_invalid = true; 2129 2130 // Make sure cursor is still visible after adjusting skipcol for "zb". 2131 if (set_topbot) { 2132 cursor_correct_sms(wp); 2133 } 2134 } 2135 2136 /// Recompute topline to put the cursor halfway across the window 2137 /// 2138 /// @param atend if true, also put the cursor halfway to the end of the file. 2139 /// 2140 void scroll_cursor_halfway(win_T *wp, bool atend, bool prefer_above) 2141 { 2142 linenr_T old_topline = wp->w_topline; 2143 lineoff_T loff = { .lnum = wp->w_cursor.lnum }; 2144 lineoff_T boff = { .lnum = wp->w_cursor.lnum }; 2145 hasFolding(wp, loff.lnum, &loff.lnum, &boff.lnum); 2146 int used = plines_win_nofill(wp, loff.lnum, true); 2147 loff.fill = 0; 2148 boff.fill = 0; 2149 linenr_T topline = loff.lnum; 2150 colnr_T skipcol = 0; 2151 2152 int want_height; 2153 bool do_sms = wp->w_p_wrap && wp->w_p_sms; 2154 if (do_sms) { 2155 // 'smoothscroll' and 'wrap' are set 2156 if (atend) { 2157 want_height = (wp->w_view_height - used) / 2; 2158 used = 0; 2159 } else { 2160 want_height = wp->w_view_height; 2161 } 2162 } 2163 2164 int topfill = 0; 2165 while (topline > 1) { 2166 // If using smoothscroll, we can precisely scroll to the 2167 // exact point where the cursor is halfway down the screen. 2168 if (do_sms) { 2169 topline_back_winheight(wp, &loff, false); 2170 if (loff.height == MAXCOL) { 2171 break; 2172 } 2173 used += loff.height; 2174 if (!atend && boff.lnum < wp->w_buffer->b_ml.ml_line_count) { 2175 botline_forw(wp, &boff); 2176 used += boff.height; 2177 } 2178 if (used > want_height) { 2179 if (used - loff.height < want_height) { 2180 topline = loff.lnum; 2181 topfill = loff.fill; 2182 skipcol = skipcol_from_plines(wp, used - want_height); 2183 } 2184 break; 2185 } 2186 topline = loff.lnum; 2187 topfill = loff.fill; 2188 continue; 2189 } 2190 2191 // If not using smoothscroll, we have to iteratively find how many 2192 // lines to scroll down to roughly fit the cursor. 2193 // This may not be right in the middle if the lines' 2194 // physical height > 1 (e.g. 'wrap' is on). 2195 2196 // Depending on "prefer_above" we add a line above or below first. 2197 // Loop twice to avoid duplicating code. 2198 bool done = false; 2199 int above = 0; 2200 int below = 0; 2201 for (int round = 1; round <= 2; round++) { 2202 if (prefer_above 2203 ? (round == 2 && below < above) 2204 : (round == 1 && below <= above)) { 2205 // add a line below the cursor 2206 if (boff.lnum < wp->w_buffer->b_ml.ml_line_count) { 2207 botline_forw(wp, &boff); 2208 used += boff.height; 2209 if (used > wp->w_view_height) { 2210 done = true; 2211 break; 2212 } 2213 below += boff.height; 2214 } else { 2215 below++; // count a "~" line 2216 if (atend) { 2217 used++; 2218 } 2219 } 2220 } 2221 2222 if (prefer_above 2223 ? (round == 1 && below >= above) 2224 : (round == 1 && below > above)) { 2225 // add a line above the cursor 2226 topline_back(wp, &loff); 2227 if (loff.height == MAXCOL) { 2228 used = MAXCOL; 2229 } else { 2230 used += loff.height; 2231 } 2232 if (used > wp->w_view_height) { 2233 done = true; 2234 break; 2235 } 2236 above += loff.height; 2237 topline = loff.lnum; 2238 topfill = loff.fill; 2239 } 2240 } 2241 if (done) { 2242 break; 2243 } 2244 } 2245 2246 if (!hasFolding(wp, topline, &wp->w_topline, NULL) 2247 && (wp->w_topline != topline || skipcol != 0 || wp->w_skipcol != 0)) { 2248 wp->w_topline = topline; 2249 if (skipcol != 0) { 2250 wp->w_skipcol = skipcol; 2251 redraw_later(wp, UPD_NOT_VALID); 2252 } else if (do_sms) { 2253 reset_skipcol(wp); 2254 } 2255 } 2256 wp->w_topfill = topfill; 2257 if (old_topline > wp->w_topline + wp->w_view_height) { 2258 wp->w_botfill = false; 2259 } 2260 check_topfill(wp, false); 2261 wp->w_valid &= ~(VALID_WROW|VALID_CROW|VALID_BOTLINE|VALID_BOTLINE_AP); 2262 wp->w_valid |= VALID_TOPLINE; 2263 } 2264 2265 // Correct the cursor position so that it is in a part of the screen at least 2266 // 'so' lines from the top and bottom, if possible. 2267 // If not possible, put it at the same position as scroll_cursor_halfway(). 2268 // When called topline must be valid! 2269 void cursor_correct(win_T *wp) 2270 { 2271 // How many lines we would like to have above/below the cursor depends on 2272 // whether the first/last line of the file is on screen. 2273 int above_wanted = get_scrolloff_value(wp); 2274 int below_wanted = get_scrolloff_value(wp); 2275 if (mouse_dragging > 0) { 2276 above_wanted = mouse_dragging - 1; 2277 below_wanted = mouse_dragging - 1; 2278 } 2279 if (wp->w_topline == 1) { 2280 above_wanted = 0; 2281 int max_off = wp->w_view_height / 2; 2282 below_wanted = MIN(below_wanted, max_off); 2283 } 2284 validate_botline_win(wp); 2285 if (wp->w_botline == wp->w_buffer->b_ml.ml_line_count + 1 2286 && mouse_dragging == 0) { 2287 below_wanted = 0; 2288 int max_off = (wp->w_view_height - 1) / 2; 2289 above_wanted = MIN(above_wanted, max_off); 2290 } 2291 2292 // If there are sufficient file-lines above and below the cursor, we can 2293 // return now. 2294 linenr_T cln = wp->w_cursor.lnum; // Cursor Line Number 2295 if (cln >= wp->w_topline + above_wanted 2296 && cln < wp->w_botline - below_wanted 2297 && !win_lines_concealed(wp)) { 2298 return; 2299 } 2300 2301 if (wp->w_p_sms && !wp->w_p_wrap) { 2302 // 'smoothscroll' is active 2303 if (wp->w_cline_height == wp->w_view_height) { 2304 // The cursor line just fits in the window, don't scroll. 2305 reset_skipcol(wp); 2306 return; 2307 } 2308 // TODO(vim): If the cursor line doesn't fit in the window then only adjust w_skipcol. 2309 } 2310 2311 // Narrow down the area where the cursor can be put by taking lines from 2312 // the top and the bottom until: 2313 // - the desired context lines are found 2314 // - the lines from the top is past the lines from the bottom 2315 linenr_T topline = wp->w_topline; 2316 linenr_T botline = wp->w_botline - 1; 2317 // count filler lines as context 2318 int above = wp->w_topfill; // screen lines above topline 2319 int below = wp->w_filler_rows; // screen lines below botline 2320 while ((above < above_wanted || below < below_wanted) && topline < botline) { 2321 if (below < below_wanted && (below <= above || above >= above_wanted)) { 2322 below += plines_win_full(wp, botline, NULL, NULL, true, true); 2323 hasFolding(wp, botline, &botline, NULL); 2324 botline--; 2325 } 2326 if (above < above_wanted && (above < below || below >= below_wanted)) { 2327 above += plines_win_nofill(wp, topline, true); 2328 hasFolding(wp, topline, NULL, &topline); 2329 2330 // Count filler lines below this line as context. 2331 if (topline < botline) { 2332 above += win_get_fill(wp, topline + 1); 2333 } 2334 topline++; 2335 } 2336 } 2337 if (topline == botline || botline == 0) { 2338 wp->w_cursor.lnum = topline; 2339 } else if (topline > botline) { 2340 wp->w_cursor.lnum = botline; 2341 } else { 2342 if (cln < topline && wp->w_topline > 1) { 2343 wp->w_cursor.lnum = topline; 2344 wp->w_valid &= 2345 ~(VALID_WROW|VALID_WCOL|VALID_CHEIGHT|VALID_CROW); 2346 } 2347 if (cln > botline && wp->w_botline <= wp->w_buffer->b_ml.ml_line_count) { 2348 wp->w_cursor.lnum = botline; 2349 wp->w_valid &= 2350 ~(VALID_WROW|VALID_WCOL|VALID_CHEIGHT|VALID_CROW); 2351 } 2352 } 2353 check_cursor_moved(wp); 2354 wp->w_valid |= VALID_TOPLINE; 2355 wp->w_viewport_invalid = true; 2356 } 2357 2358 /// Decide how much overlap to use for page-up or page-down scrolling. 2359 /// This is symmetric, so that doing both keeps the same lines displayed. 2360 /// Three lines are examined: 2361 /// 2362 /// before CTRL-F after CTRL-F / before CTRL-B 2363 /// etc. l1 2364 /// l1 last but one line ------------ 2365 /// l2 last text line l2 top text line 2366 /// ------------- l3 second text line 2367 /// l3 etc. 2368 static int get_scroll_overlap(Direction dir) 2369 { 2370 lineoff_T loff; 2371 int min_height = curwin->w_view_height - 2; 2372 2373 validate_botline_win(curwin); 2374 if ((dir == BACKWARD && curwin->w_topline == 1) 2375 || (dir == FORWARD && curwin->w_botline > curbuf->b_ml.ml_line_count)) { 2376 return min_height + 2; // no overlap, still handle 'smoothscroll' 2377 } 2378 2379 loff.lnum = dir == FORWARD ? curwin->w_botline : curwin->w_topline - 1; 2380 loff.fill = win_get_fill(curwin, loff.lnum + (dir == BACKWARD)) 2381 - (dir == FORWARD ? curwin->w_filler_rows : curwin->w_topfill); 2382 loff.height = loff.fill > 0 ? 1 : plines_win_nofill(curwin, loff.lnum, true); 2383 2384 int h1 = loff.height; 2385 if (h1 > min_height) { 2386 return min_height + 2; // no overlap 2387 } 2388 if (dir == FORWARD) { 2389 topline_back(curwin, &loff); 2390 } else { 2391 botline_forw(curwin, &loff); 2392 } 2393 2394 int h2 = loff.height; 2395 if (h2 == MAXCOL || h2 + h1 > min_height) { 2396 return min_height + 2; // no overlap 2397 } 2398 if (dir == FORWARD) { 2399 topline_back(curwin, &loff); 2400 } else { 2401 botline_forw(curwin, &loff); 2402 } 2403 2404 int h3 = loff.height; 2405 if (h3 == MAXCOL || h3 + h2 > min_height) { 2406 return min_height + 2; // no overlap 2407 } 2408 if (dir == FORWARD) { 2409 topline_back(curwin, &loff); 2410 } else { 2411 botline_forw(curwin, &loff); 2412 } 2413 2414 int h4 = loff.height; 2415 if (h4 == MAXCOL || h4 + h3 + h2 > min_height || h3 + h2 + h1 > min_height) { 2416 return min_height + 1; // 1 line overlap 2417 } else { 2418 return min_height; // 2 lines overlap 2419 } 2420 } 2421 2422 /// Scroll "count" lines with 'smoothscroll' in direction "dir". Return true 2423 /// when scrolling happened. Adjust "curscount" for scrolling different amount 2424 /// of lines when 'smoothscroll' is disabled. 2425 static bool scroll_with_sms(Direction dir, int count, int *curscount) 2426 { 2427 int prev_sms = curwin->w_p_sms; 2428 colnr_T prev_skipcol = curwin->w_skipcol; 2429 linenr_T prev_topline = curwin->w_topline; 2430 int prev_topfill = curwin->w_topfill; 2431 2432 curwin->w_p_sms = true; 2433 scroll_redraw(dir == FORWARD, count); 2434 2435 // Not actually smoothscrolling but ended up with partially visible line. 2436 // Continue scrolling until skipcol is zero. 2437 if (!prev_sms && curwin->w_skipcol > 0) { 2438 int fixdir = dir; 2439 // Reverse the scroll direction when topline already changed. One line 2440 // extra for scrolling backward so that consuming skipcol is symmetric. 2441 if (labs(curwin->w_topline - prev_topline) > (dir == BACKWARD)) { 2442 fixdir = dir * -1; 2443 } 2444 2445 int width1 = curwin->w_view_width - win_col_off(curwin); 2446 int width2 = width1 + win_col_off2(curwin); 2447 count = 1 + (curwin->w_skipcol - width1 - 1) / width2; 2448 if (fixdir == FORWARD) { 2449 count = 1 + (linetabsize_eol(curwin, curwin->w_topline) 2450 - curwin->w_skipcol - width1 + width2 - 1) / width2; 2451 } 2452 scroll_redraw(fixdir == FORWARD, count); 2453 *curscount += count * (fixdir == dir ? 1 : -1); 2454 } 2455 curwin->w_p_sms = prev_sms; 2456 2457 return curwin->w_topline != prev_topline 2458 || curwin->w_topfill != prev_topfill 2459 || curwin->w_skipcol != prev_skipcol; 2460 } 2461 2462 /// Move screen "count" (half) pages up ("dir" is BACKWARD) or down ("dir" is 2463 /// FORWARD) and update the screen. Handle moving the cursor and not scrolling 2464 /// to reveal end of buffer lines for half-page scrolling with CTRL-D and CTRL-U. 2465 /// 2466 /// @return FAIL for failure, OK otherwise. 2467 int pagescroll(Direction dir, int count, bool half) 2468 { 2469 bool did_move = false; 2470 int buflen = curbuf->b_ml.ml_line_count; 2471 colnr_T prev_col = curwin->w_cursor.col; 2472 colnr_T prev_curswant = curwin->w_curswant; 2473 linenr_T prev_lnum = curwin->w_cursor.lnum; 2474 oparg_T oa = { 0 }; 2475 cmdarg_T ca = { 0 }; 2476 ca.oap = &oa; 2477 2478 if (half) { 2479 // Scroll [count], 'scroll' or current window height lines. 2480 if (count) { 2481 curwin->w_p_scr = MIN(curwin->w_view_height, count); 2482 } 2483 count = MIN(curwin->w_view_height, (int)curwin->w_p_scr); 2484 2485 int curscount = count; 2486 // Adjust count so as to not reveal end of buffer lines. 2487 if (dir == FORWARD 2488 && (curwin->w_topline + curwin->w_view_height + count > buflen 2489 || win_lines_concealed(curwin))) { 2490 int n = plines_correct_topline(curwin, curwin->w_topline, NULL, false, NULL); 2491 if (n - count < curwin->w_view_height && curwin->w_topline < buflen) { 2492 n += plines_m_win(curwin, curwin->w_topline + 1, buflen, curwin->w_view_height + count); 2493 } 2494 if (n < curwin->w_view_height + count) { 2495 count = n - curwin->w_view_height; 2496 } 2497 } 2498 2499 // (Try to) scroll the window unless already at the end of the buffer. 2500 if (count > 0) { 2501 did_move = scroll_with_sms(dir, count, &curscount); 2502 curwin->w_cursor.lnum = prev_lnum; 2503 curwin->w_cursor.col = prev_col; 2504 curwin->w_curswant = prev_curswant; 2505 } 2506 2507 // Move the cursor the same amount of screen lines, skipping over 2508 // concealed lines as those were not included in "curscount". 2509 if (curwin->w_p_wrap) { 2510 nv_screengo(&oa, dir, curscount, true); 2511 } else if (dir == FORWARD) { 2512 cursor_down_inner(curwin, curscount, true); 2513 } else { 2514 cursor_up_inner(curwin, curscount, true); 2515 } 2516 } else { 2517 // Scroll [count] times 'window' or current window height lines. 2518 count *= ((ONE_WINDOW && p_window > 0 && p_window < Rows - 1) 2519 ? MAX(1, (int)p_window - 2) : get_scroll_overlap(dir)); 2520 did_move = scroll_with_sms(dir, count, &count); 2521 2522 if (did_move) { 2523 // Place cursor at top or bottom of window. 2524 validate_botline_win(curwin); 2525 linenr_T lnum = (dir == FORWARD ? curwin->w_topline : curwin->w_botline - 1); 2526 // In silent Ex mode the value of w_botline - 1 may be 0, 2527 // but cursor lnum needs to be at least 1. 2528 curwin->w_cursor.lnum = MAX(lnum, 1); 2529 } 2530 } 2531 2532 if (get_scrolloff_value(curwin) > 0) { 2533 cursor_correct(curwin); 2534 } 2535 // Move cursor to first line of closed fold. 2536 foldAdjustCursor(curwin); 2537 2538 did_move = did_move 2539 || prev_col != curwin->w_cursor.col 2540 || prev_lnum != curwin->w_cursor.lnum; 2541 2542 // Error if both the viewport and cursor did not change. 2543 if (!did_move) { 2544 beep_flush(); 2545 } else if (!curwin->w_p_sms) { 2546 beginline(BL_SOL | BL_FIX); 2547 } else if (p_sol) { 2548 nv_g_home_m_cmd(&ca); 2549 } 2550 2551 return did_move ? OK : FAIL; 2552 } 2553 2554 void do_check_cursorbind(void) 2555 { 2556 static win_T *prev_curwin = NULL; 2557 static pos_T prev_cursor = { 0, 0, 0 }; 2558 2559 if (curwin == prev_curwin && equalpos(curwin->w_cursor, prev_cursor)) { 2560 return; 2561 } 2562 prev_curwin = curwin; 2563 prev_cursor = curwin->w_cursor; 2564 2565 linenr_T line = curwin->w_cursor.lnum; 2566 colnr_T col = curwin->w_cursor.col; 2567 colnr_T coladd = curwin->w_cursor.coladd; 2568 colnr_T curswant = curwin->w_curswant; 2569 bool set_curswant = curwin->w_set_curswant; 2570 win_T *old_curwin = curwin; 2571 buf_T *old_curbuf = curbuf; 2572 int old_VIsual_select = VIsual_select; 2573 int old_VIsual_active = VIsual_active; 2574 2575 // loop through the cursorbound windows 2576 VIsual_select = VIsual_active = false; 2577 FOR_ALL_WINDOWS_IN_TAB(wp, curtab) { 2578 curwin = wp; 2579 curbuf = curwin->w_buffer; 2580 // skip original window and windows with 'nocursorbind' 2581 if (curwin != old_curwin && curwin->w_p_crb) { 2582 if (curwin->w_p_diff) { 2583 curwin->w_cursor.lnum = 2584 diff_get_corresponding_line(old_curbuf, line); 2585 } else { 2586 curwin->w_cursor.lnum = line; 2587 } 2588 curwin->w_cursor.col = col; 2589 curwin->w_cursor.coladd = coladd; 2590 curwin->w_curswant = curswant; 2591 curwin->w_set_curswant = set_curswant; 2592 2593 // Make sure the cursor is in a valid position. Temporarily set 2594 // "restart_edit" to allow the cursor to be beyond the EOL. 2595 { 2596 int restart_edit_save = restart_edit; 2597 restart_edit = true; 2598 check_cursor(curwin); 2599 2600 // Avoid a scroll here for the cursor position, 'scrollbind' is 2601 // more important. 2602 if (!curwin->w_p_scb) { 2603 validate_cursor(curwin); 2604 } 2605 2606 restart_edit = restart_edit_save; 2607 } 2608 // Correct cursor for multi-byte character. 2609 mb_adjust_cursor(); 2610 redraw_later(curwin, UPD_VALID); 2611 2612 // Only scroll when 'scrollbind' hasn't done this. 2613 if (!curwin->w_p_scb) { 2614 update_topline(curwin); 2615 } 2616 curwin->w_redr_status = true; 2617 } 2618 } 2619 2620 // reset current-window 2621 VIsual_select = old_VIsual_select; 2622 VIsual_active = old_VIsual_active; 2623 curwin = old_curwin; 2624 curbuf = old_curbuf; 2625 }