1// This is an open source non-commercial project. Dear PVS-Studio, please check
2// it. PVS-Studio Static Code Analyzer for C, C++ and C#: http://www.viva64.com
3
4/*
5 * move.c: Functions for moving the cursor and scrolling text.
6 *
7 * There are two ways to move the cursor:
8 * 1. Move the cursor directly, the text is scrolled to keep the cursor in the
9 * window.
10 * 2. Scroll the text, the cursor is moved into the text visible in the
11 * window.
12 * The 'scrolloff' option makes this a bit complicated.
13 */
14
15#include <assert.h>
16#include <inttypes.h>
17#include <stdbool.h>
18
19#include "nvim/ascii.h"
20#include "nvim/move.h"
21#include "nvim/charset.h"
22#include "nvim/cursor.h"
23#include "nvim/diff.h"
24#include "nvim/edit.h"
25#include "nvim/fold.h"
26#include "nvim/mbyte.h"
27#include "nvim/memline.h"
28#include "nvim/misc1.h"
29#include "nvim/option.h"
30#include "nvim/popupmnu.h"
31#include "nvim/screen.h"
32#include "nvim/strings.h"
33#include "nvim/window.h"
34
35typedef struct {
36 linenr_T lnum; /* line number */
37 int fill; /* filler lines */
38 int height; /* height of added line */
39} lineoff_T;
40
41#ifdef INCLUDE_GENERATED_DECLARATIONS
42# include "move.c.generated.h"
43#endif
44
45
46/*
47 * Compute wp->w_botline for the current wp->w_topline. Can be called after
48 * wp->w_topline changed.
49 */
50static void comp_botline(win_T *wp)
51{
52 linenr_T lnum;
53 int done;
54
55 /*
56 * If w_cline_row is valid, start there.
57 * Otherwise have to start at w_topline.
58 */
59 check_cursor_moved(wp);
60 if (wp->w_valid & VALID_CROW) {
61 lnum = wp->w_cursor.lnum;
62 done = wp->w_cline_row;
63 } else {
64 lnum = wp->w_topline;
65 done = 0;
66 }
67
68 for (; lnum <= wp->w_buffer->b_ml.ml_line_count; ++lnum) {
69 int n;
70 linenr_T last = lnum;
71 bool folded = hasFoldingWin(wp, lnum, NULL, &last, true, NULL);
72 if (folded) {
73 n = 1;
74 } else if (lnum == wp->w_topline) {
75 n = plines_win_nofill(wp, lnum, true) + wp->w_topfill;
76 } else {
77 n = plines_win(wp, lnum, true);
78 }
79 if (lnum <= wp->w_cursor.lnum && last >= wp->w_cursor.lnum) {
80 wp->w_cline_row = done;
81 wp->w_cline_height = n;
82 wp->w_cline_folded = folded;
83 redraw_for_cursorline(wp);
84 wp->w_valid |= (VALID_CROW|VALID_CHEIGHT);
85 }
86 if (done + n > wp->w_height_inner) {
87 break;
88 }
89 done += n;
90 lnum = last;
91 }
92
93 /* wp->w_botline is the line that is just below the window */
94 wp->w_botline = lnum;
95 wp->w_valid |= VALID_BOTLINE|VALID_BOTLINE_AP;
96
97 set_empty_rows(wp, done);
98
99 win_check_anchored_floats(wp);
100}
101
102void reset_cursorline(void)
103{
104 curwin->w_last_cursorline = 0;
105}
106
107// Redraw when w_cline_row changes and 'relativenumber' or 'cursorline' is set.
108void redraw_for_cursorline(win_T *wp)
109 FUNC_ATTR_NONNULL_ALL
110{
111 if ((wp->w_p_rnu || win_cursorline_standout(wp))
112 && (wp->w_valid & VALID_CROW) == 0
113 && !pum_visible()) {
114 if (wp->w_p_rnu) {
115 // win_line() will redraw the number column only.
116 redraw_win_later(wp, VALID);
117 }
118 if (win_cursorline_standout(wp)) {
119 if (wp->w_redr_type <= VALID && wp->w_last_cursorline != 0) {
120 // "w_last_cursorline" may be outdated, worst case we redraw
121 // too much. This is optimized for moving the cursor around in
122 // the current window.
123 redrawWinline(wp, wp->w_last_cursorline);
124 redrawWinline(wp, wp->w_cursor.lnum);
125 } else {
126 redraw_win_later(wp, SOME_VALID);
127 }
128 }
129 }
130}
131
132/*
133 * Update curwin->w_topline and redraw if necessary.
134 * Used to update the screen before printing a message.
135 */
136void update_topline_redraw(void)
137{
138 update_topline();
139 if (must_redraw)
140 update_screen(0);
141}
142
143/*
144 * Update curwin->w_topline to move the cursor onto the screen.
145 */
146void update_topline(void)
147{
148 linenr_T old_topline;
149 int old_topfill;
150 bool check_topline = false;
151 bool check_botline = false;
152 long save_so = p_so;
153
154 // If there is no valid screen and when the window height is zero just use
155 // the cursor line.
156 if (!default_grid.chars || curwin->w_height_inner == 0) {
157 curwin->w_topline = curwin->w_cursor.lnum;
158 curwin->w_botline = curwin->w_topline;
159 curwin->w_valid |= VALID_BOTLINE|VALID_BOTLINE_AP;
160 curwin->w_scbind_pos = 1;
161 return;
162 }
163
164 check_cursor_moved(curwin);
165 if (curwin->w_valid & VALID_TOPLINE)
166 return;
167
168 /* When dragging with the mouse, don't scroll that quickly */
169 if (mouse_dragging > 0)
170 p_so = mouse_dragging - 1;
171
172 old_topline = curwin->w_topline;
173 old_topfill = curwin->w_topfill;
174
175 // If the buffer is empty, always set topline to 1.
176 if (BUFEMPTY()) { // special case - file is empty
177 if (curwin->w_topline != 1) {
178 redraw_later(NOT_VALID);
179 }
180 curwin->w_topline = 1;
181 curwin->w_botline = 2;
182 curwin->w_valid |= VALID_BOTLINE|VALID_BOTLINE_AP;
183 curwin->w_scbind_pos = 1;
184 }
185 /*
186 * If the cursor is above or near the top of the window, scroll the window
187 * to show the line the cursor is in, with 'scrolloff' context.
188 */
189 else {
190 if (curwin->w_topline > 1) {
191 /* If the cursor is above topline, scrolling is always needed.
192 * If the cursor is far below topline and there is no folding,
193 * scrolling down is never needed. */
194 if (curwin->w_cursor.lnum < curwin->w_topline)
195 check_topline = true;
196 else if (check_top_offset())
197 check_topline = true;
198 }
199 /* Check if there are more filler lines than allowed. */
200 if (!check_topline && curwin->w_topfill > diff_check_fill(curwin,
201 curwin->w_topline))
202 check_topline = true;
203
204 if (check_topline) {
205 int halfheight = curwin->w_height_inner / 2 - 1;
206 if (halfheight < 2) {
207 halfheight = 2;
208 }
209 long n;
210 if (hasAnyFolding(curwin)) {
211 /* Count the number of logical lines between the cursor and
212 * topline + p_so (approximation of how much will be
213 * scrolled). */
214 n = 0;
215 for (linenr_T lnum = curwin->w_cursor.lnum;
216 lnum < curwin->w_topline + p_so; ++lnum) {
217 ++n;
218 /* stop at end of file or when we know we are far off */
219 if (lnum >= curbuf->b_ml.ml_line_count || n >= halfheight)
220 break;
221 (void)hasFolding(lnum, NULL, &lnum);
222 }
223 } else
224 n = curwin->w_topline + p_so - curwin->w_cursor.lnum;
225
226 /* If we weren't very close to begin with, we scroll to put the
227 * cursor in the middle of the window. Otherwise put the cursor
228 * near the top of the window. */
229 if (n >= halfheight) {
230 scroll_cursor_halfway(false);
231 } else {
232 scroll_cursor_top(scrolljump_value(), false);
233 check_botline = true;
234 }
235 } else {
236 /* Make sure topline is the first line of a fold. */
237 (void)hasFolding(curwin->w_topline, &curwin->w_topline, NULL);
238 check_botline = true;
239 }
240 }
241
242 /*
243 * If the cursor is below the bottom of the window, scroll the window
244 * to put the cursor on the window.
245 * When w_botline is invalid, recompute it first, to avoid a redraw later.
246 * If w_botline was approximated, we might need a redraw later in a few
247 * cases, but we don't want to spend (a lot of) time recomputing w_botline
248 * for every small change.
249 */
250 if (check_botline) {
251 if (!(curwin->w_valid & VALID_BOTLINE_AP))
252 validate_botline();
253
254 if (curwin->w_botline <= curbuf->b_ml.ml_line_count) {
255 if (curwin->w_cursor.lnum < curwin->w_botline) {
256 if (((long)curwin->w_cursor.lnum
257 >= (long)curwin->w_botline - p_so
258 || hasAnyFolding(curwin)
259 )) {
260 lineoff_T loff;
261
262 /* Cursor is (a few lines) above botline, check if there are
263 * 'scrolloff' window lines below the cursor. If not, need to
264 * scroll. */
265 int n = curwin->w_empty_rows;
266 loff.lnum = curwin->w_cursor.lnum;
267 /* In a fold go to its last line. */
268 (void)hasFolding(loff.lnum, NULL, &loff.lnum);
269 loff.fill = 0;
270 n += curwin->w_filler_rows;
271 loff.height = 0;
272 while (loff.lnum < curwin->w_botline
273 && (loff.lnum + 1 < curwin->w_botline || loff.fill == 0)
274 ) {
275 n += loff.height;
276 if (n >= p_so)
277 break;
278 botline_forw(&loff);
279 }
280 if (n >= p_so)
281 /* sufficient context, no need to scroll */
282 check_botline = false;
283 } else {
284 /* sufficient context, no need to scroll */
285 check_botline = false;
286 }
287 }
288 if (check_botline) {
289 long line_count = 0;
290 if (hasAnyFolding(curwin)) {
291 /* Count the number of logical lines between the cursor and
292 * botline - p_so (approximation of how much will be
293 * scrolled). */
294 for (linenr_T lnum = curwin->w_cursor.lnum;
295 lnum >= curwin->w_botline - p_so; lnum--) {
296 line_count++;
297 // stop at end of file or when we know we are far off
298 if (lnum <= 0 || line_count > curwin->w_height_inner + 1) {
299 break;
300 }
301 (void)hasFolding(lnum, &lnum, NULL);
302 }
303 } else
304 line_count = curwin->w_cursor.lnum - curwin->w_botline
305 + 1 + p_so;
306 if (line_count <= curwin->w_height_inner + 1) {
307 scroll_cursor_bot(scrolljump_value(), false);
308 } else {
309 scroll_cursor_halfway(false);
310 }
311 }
312 }
313 }
314 curwin->w_valid |= VALID_TOPLINE;
315 win_check_anchored_floats(curwin);
316
317 /*
318 * Need to redraw when topline changed.
319 */
320 if (curwin->w_topline != old_topline
321 || curwin->w_topfill != old_topfill
322 ) {
323 dollar_vcol = -1;
324 if (curwin->w_skipcol != 0) {
325 curwin->w_skipcol = 0;
326 redraw_later(NOT_VALID);
327 } else
328 redraw_later(VALID);
329 /* May need to set w_skipcol when cursor in w_topline. */
330 if (curwin->w_cursor.lnum == curwin->w_topline)
331 validate_cursor();
332 }
333
334 p_so = save_so;
335}
336
337/*
338 * Update win->w_topline to move the cursor onto the screen.
339 */
340void update_topline_win(win_T* win)
341{
342 win_T *save_curwin;
343 switch_win(&save_curwin, NULL, win, NULL, true);
344 update_topline();
345 restore_win(save_curwin, NULL, true);
346}
347
348/*
349 * Return the scrolljump value to use for the current window.
350 * When 'scrolljump' is positive use it as-is.
351 * When 'scrolljump' is negative use it as a percentage of the window height.
352 */
353static int scrolljump_value(void)
354{
355 long result = p_sj >= 0 ? p_sj : (curwin->w_height_inner * -p_sj) / 100;
356 assert(result <= INT_MAX);
357 return (int)result;
358}
359
360/*
361 * Return true when there are not 'scrolloff' lines above the cursor for the
362 * current window.
363 */
364static bool check_top_offset(void)
365{
366 if (curwin->w_cursor.lnum < curwin->w_topline + p_so
367 || hasAnyFolding(curwin)
368 ) {
369 lineoff_T loff;
370 loff.lnum = curwin->w_cursor.lnum;
371 loff.fill = 0;
372 int n = curwin->w_topfill; /* always have this context */
373 /* Count the visible screen lines above the cursor line. */
374 while (n < p_so) {
375 topline_back(&loff);
376 /* Stop when included a line above the window. */
377 if (loff.lnum < curwin->w_topline
378 || (loff.lnum == curwin->w_topline && loff.fill > 0)
379 )
380 break;
381 n += loff.height;
382 }
383 if (n < p_so)
384 return true;
385 }
386 return false;
387}
388
389void update_curswant(void)
390{
391 if (curwin->w_set_curswant) {
392 validate_virtcol();
393 curwin->w_curswant = curwin->w_virtcol;
394 curwin->w_set_curswant = false;
395 }
396}
397
398/*
399 * Check if the cursor has moved. Set the w_valid flag accordingly.
400 */
401void check_cursor_moved(win_T *wp)
402{
403 if (wp->w_cursor.lnum != wp->w_valid_cursor.lnum) {
404 wp->w_valid &= ~(VALID_WROW|VALID_WCOL|VALID_VIRTCOL
405 |VALID_CHEIGHT|VALID_CROW|VALID_TOPLINE);
406 wp->w_valid_cursor = wp->w_cursor;
407 wp->w_valid_leftcol = wp->w_leftcol;
408 } else if (wp->w_cursor.col != wp->w_valid_cursor.col
409 || wp->w_leftcol != wp->w_valid_leftcol
410 || wp->w_cursor.coladd != wp->w_valid_cursor.coladd
411 ) {
412 wp->w_valid &= ~(VALID_WROW|VALID_WCOL|VALID_VIRTCOL);
413 wp->w_valid_cursor.col = wp->w_cursor.col;
414 wp->w_valid_leftcol = wp->w_leftcol;
415 wp->w_valid_cursor.coladd = wp->w_cursor.coladd;
416 }
417}
418
419/*
420 * Call this function when some window settings have changed, which require
421 * the cursor position, botline and topline to be recomputed and the window to
422 * be redrawn. E.g, when changing the 'wrap' option or folding.
423 */
424void changed_window_setting(void)
425{
426 changed_window_setting_win(curwin);
427}
428
429void changed_window_setting_win(win_T *wp)
430{
431 wp->w_lines_valid = 0;
432 changed_line_abv_curs_win(wp);
433 wp->w_valid &= ~(VALID_BOTLINE|VALID_BOTLINE_AP|VALID_TOPLINE);
434 redraw_win_later(wp, NOT_VALID);
435}
436
437/*
438 * Set wp->w_topline to a certain number.
439 */
440void set_topline(win_T *wp, linenr_T lnum)
441{
442 /* go to first of folded lines */
443 (void)hasFoldingWin(wp, lnum, &lnum, NULL, true, NULL);
444 /* Approximate the value of w_botline */
445 wp->w_botline += lnum - wp->w_topline;
446 wp->w_topline = lnum;
447 wp->w_topline_was_set = true;
448 wp->w_topfill = 0;
449 wp->w_valid &= ~(VALID_WROW|VALID_CROW|VALID_BOTLINE|VALID_TOPLINE);
450 /* Don't set VALID_TOPLINE here, 'scrolloff' needs to be checked. */
451 redraw_later(VALID);
452}
453
454/*
455 * Call this function when the length of the cursor line (in screen
456 * characters) has changed, and the change is before the cursor.
457 * Need to take care of w_botline separately!
458 */
459void changed_cline_bef_curs(void)
460{
461 curwin->w_valid &= ~(VALID_WROW|VALID_WCOL|VALID_VIRTCOL
462 |VALID_CHEIGHT|VALID_TOPLINE);
463}
464
465void changed_cline_bef_curs_win(win_T *wp)
466{
467 wp->w_valid &= ~(VALID_WROW|VALID_WCOL|VALID_VIRTCOL
468 |VALID_CHEIGHT|VALID_TOPLINE);
469}
470
471/*
472 * Call this function when the length of a line (in screen characters) above
473 * the cursor have changed.
474 * Need to take care of w_botline separately!
475 */
476void changed_line_abv_curs(void)
477{
478 curwin->w_valid &= ~(VALID_WROW|VALID_WCOL|VALID_VIRTCOL|VALID_CROW
479 |VALID_CHEIGHT|VALID_TOPLINE);
480}
481
482void changed_line_abv_curs_win(win_T *wp)
483{
484 wp->w_valid &= ~(VALID_WROW|VALID_WCOL|VALID_VIRTCOL|VALID_CROW
485 |VALID_CHEIGHT|VALID_TOPLINE);
486}
487
488/*
489 * Make sure the value of curwin->w_botline is valid.
490 */
491void validate_botline(void)
492{
493 if (!(curwin->w_valid & VALID_BOTLINE))
494 comp_botline(curwin);
495}
496
497/*
498 * Mark curwin->w_botline as invalid (because of some change in the buffer).
499 */
500void invalidate_botline(void)
501{
502 curwin->w_valid &= ~(VALID_BOTLINE|VALID_BOTLINE_AP);
503}
504
505void invalidate_botline_win(win_T *wp)
506{
507 wp->w_valid &= ~(VALID_BOTLINE|VALID_BOTLINE_AP);
508}
509
510void approximate_botline_win(win_T *wp)
511{
512 wp->w_valid &= ~VALID_BOTLINE;
513}
514
515/*
516 * Return true if curwin->w_wrow and curwin->w_wcol are valid.
517 */
518int cursor_valid(void)
519{
520 check_cursor_moved(curwin);
521 return (curwin->w_valid & (VALID_WROW|VALID_WCOL)) ==
522 (VALID_WROW|VALID_WCOL);
523}
524
525/*
526 * Validate cursor position. Makes sure w_wrow and w_wcol are valid.
527 * w_topline must be valid, you may need to call update_topline() first!
528 */
529void validate_cursor(void)
530{
531 check_cursor_moved(curwin);
532 if ((curwin->w_valid & (VALID_WCOL|VALID_WROW)) != (VALID_WCOL|VALID_WROW))
533 curs_columns(true);
534}
535
536/*
537 * Compute wp->w_cline_row and wp->w_cline_height, based on the current value
538 * of wp->w_topline.
539 */
540static void curs_rows(win_T *wp)
541{
542 /* Check if wp->w_lines[].wl_size is invalid */
543 int all_invalid = (!redrawing()
544 || wp->w_lines_valid == 0
545 || wp->w_lines[0].wl_lnum > wp->w_topline);
546 int i = 0;
547 wp->w_cline_row = 0;
548 for (linenr_T lnum = wp->w_topline; lnum < wp->w_cursor.lnum; ++i) {
549 bool valid = false;
550 if (!all_invalid && i < wp->w_lines_valid) {
551 if (wp->w_lines[i].wl_lnum < lnum || !wp->w_lines[i].wl_valid)
552 continue; /* skip changed or deleted lines */
553 if (wp->w_lines[i].wl_lnum == lnum) {
554 /* Check for newly inserted lines below this row, in which
555 * case we need to check for folded lines. */
556 if (!wp->w_buffer->b_mod_set
557 || wp->w_lines[i].wl_lastlnum < wp->w_cursor.lnum
558 || wp->w_buffer->b_mod_top
559 > wp->w_lines[i].wl_lastlnum + 1)
560 valid = true;
561 } else if (wp->w_lines[i].wl_lnum > lnum) {
562 --i; /* hold at inserted lines */
563 }
564 }
565 if (valid
566 && (lnum != wp->w_topline || !wp->w_p_diff)
567 ) {
568 lnum = wp->w_lines[i].wl_lastlnum + 1;
569 /* Cursor inside folded lines, don't count this row */
570 if (lnum > wp->w_cursor.lnum)
571 break;
572 wp->w_cline_row += wp->w_lines[i].wl_size;
573 } else {
574 long fold_count = foldedCount(wp, lnum, NULL);
575 if (fold_count) {
576 lnum += fold_count;
577 if (lnum > wp->w_cursor.lnum)
578 break;
579 ++wp->w_cline_row;
580 } else if (lnum == wp->w_topline) {
581 wp->w_cline_row += plines_win_nofill(wp, lnum++, true)
582 + wp->w_topfill;
583 } else {
584 wp->w_cline_row += plines_win(wp, lnum++, true);
585 }
586 }
587 }
588
589 check_cursor_moved(wp);
590 if (!(wp->w_valid & VALID_CHEIGHT)) {
591 if (all_invalid
592 || i == wp->w_lines_valid
593 || (i < wp->w_lines_valid
594 && (!wp->w_lines[i].wl_valid
595 || wp->w_lines[i].wl_lnum != wp->w_cursor.lnum))) {
596 if (wp->w_cursor.lnum == wp->w_topline)
597 wp->w_cline_height = plines_win_nofill(wp, wp->w_cursor.lnum,
598 true) + wp->w_topfill;
599 else
600 wp->w_cline_height = plines_win(wp, wp->w_cursor.lnum, true);
601 wp->w_cline_folded = hasFoldingWin(wp, wp->w_cursor.lnum,
602 NULL, NULL, true, NULL);
603 } else if (i > wp->w_lines_valid) {
604 /* a line that is too long to fit on the last screen line */
605 wp->w_cline_height = 0;
606 wp->w_cline_folded = hasFoldingWin(wp, wp->w_cursor.lnum,
607 NULL, NULL, true, NULL);
608 } else {
609 wp->w_cline_height = wp->w_lines[i].wl_size;
610 wp->w_cline_folded = wp->w_lines[i].wl_folded;
611 }
612 }
613
614 redraw_for_cursorline(curwin);
615 wp->w_valid |= VALID_CROW|VALID_CHEIGHT;
616}
617
618/*
619 * Validate curwin->w_virtcol only.
620 */
621void validate_virtcol(void)
622{
623 validate_virtcol_win(curwin);
624}
625
626/*
627 * Validate wp->w_virtcol only.
628 */
629void validate_virtcol_win(win_T *wp)
630{
631 check_cursor_moved(wp);
632 if (!(wp->w_valid & VALID_VIRTCOL)) {
633 getvvcol(wp, &wp->w_cursor, NULL, &(wp->w_virtcol), NULL);
634 wp->w_valid |= VALID_VIRTCOL;
635 if (wp->w_p_cuc
636 && !pum_visible()
637 )
638 redraw_win_later(wp, SOME_VALID);
639 }
640}
641
642/*
643 * Validate curwin->w_cline_height only.
644 */
645static void validate_cheight(void)
646{
647 check_cursor_moved(curwin);
648 if (!(curwin->w_valid & VALID_CHEIGHT)) {
649 if (curwin->w_cursor.lnum == curwin->w_topline)
650 curwin->w_cline_height = plines_nofill(curwin->w_cursor.lnum)
651 + curwin->w_topfill;
652 else
653 curwin->w_cline_height = plines(curwin->w_cursor.lnum);
654 curwin->w_cline_folded = hasFolding(curwin->w_cursor.lnum, NULL, NULL);
655 curwin->w_valid |= VALID_CHEIGHT;
656 }
657}
658
659/*
660 * Validate w_wcol and w_virtcol only.
661 */
662void validate_cursor_col(void)
663{
664 validate_virtcol();
665 if (!(curwin->w_valid & VALID_WCOL)) {
666 colnr_T col = curwin->w_virtcol;
667 colnr_T off = curwin_col_off();
668 col += off;
669 int width = curwin->w_width_inner - off + curwin_col_off2();
670
671 // long line wrapping, adjust curwin->w_wrow
672 if (curwin->w_p_wrap && col >= (colnr_T)curwin->w_width_inner
673 && width > 0) {
674 // use same formula as what is used in curs_columns()
675 col -= ((col - curwin->w_width_inner) / width + 1) * width;
676 }
677 if (col > (int)curwin->w_leftcol) {
678 col -= curwin->w_leftcol;
679 } else {
680 col = 0;
681 }
682 curwin->w_wcol = col;
683
684 curwin->w_valid |= VALID_WCOL;
685 }
686}
687
688/*
689 * Compute offset of a window, occupied by absolute or relative line number,
690 * fold column and sign column (these don't move when scrolling horizontally).
691 */
692int win_col_off(win_T *wp)
693{
694 return ((wp->w_p_nu || wp->w_p_rnu) ? number_width(wp) + 1 : 0)
695 + (cmdwin_type == 0 || wp != curwin ? 0 : 1)
696 + (int)wp->w_p_fdc
697 + (win_signcol_count(wp) * win_signcol_width(wp));
698}
699
700int curwin_col_off(void)
701{
702 return win_col_off(curwin);
703}
704
705/*
706 * Return the difference in column offset for the second screen line of a
707 * wrapped line. It's 8 if 'number' or 'relativenumber' is on and 'n' is in
708 * 'cpoptions'.
709 */
710int win_col_off2(win_T *wp)
711{
712 if ((wp->w_p_nu || wp->w_p_rnu) && vim_strchr(p_cpo, CPO_NUMCOL) != NULL)
713 return number_width(wp) + 1;
714 return 0;
715}
716
717int curwin_col_off2(void)
718{
719 return win_col_off2(curwin);
720}
721
722/*
723 * compute curwin->w_wcol and curwin->w_virtcol.
724 * Also updates curwin->w_wrow and curwin->w_cline_row.
725 * Also updates curwin->w_leftcol.
726 */
727void curs_columns(
728 int may_scroll /* when true, may scroll horizontally */
729)
730{
731 int n;
732 int width = 0;
733 colnr_T startcol;
734 colnr_T endcol;
735 colnr_T prev_skipcol;
736
737 /*
738 * First make sure that w_topline is valid (after moving the cursor).
739 */
740 update_topline();
741
742 // Next make sure that w_cline_row is valid.
743 if (!(curwin->w_valid & VALID_CROW)) {
744 curs_rows(curwin);
745 }
746
747 /*
748 * Compute the number of virtual columns.
749 */
750 if (curwin->w_cline_folded)
751 /* In a folded line the cursor is always in the first column */
752 startcol = curwin->w_virtcol = endcol = curwin->w_leftcol;
753 else
754 getvvcol(curwin, &curwin->w_cursor,
755 &startcol, &(curwin->w_virtcol), &endcol);
756
757 /* remove '$' from change command when cursor moves onto it */
758 if (startcol > dollar_vcol)
759 dollar_vcol = -1;
760
761 int extra = curwin_col_off();
762 curwin->w_wcol = curwin->w_virtcol + extra;
763 endcol += extra;
764
765 /*
766 * Now compute w_wrow, counting screen lines from w_cline_row.
767 */
768 curwin->w_wrow = curwin->w_cline_row;
769
770 int textwidth = curwin->w_width_inner - extra;
771 if (textwidth <= 0) {
772 // No room for text, put cursor in last char of window.
773 curwin->w_wcol = curwin->w_width_inner - 1;
774 curwin->w_wrow = curwin->w_height_inner - 1;
775 } else if (curwin->w_p_wrap
776 && curwin->w_width_inner != 0
777 ) {
778 width = textwidth + curwin_col_off2();
779
780 // long line wrapping, adjust curwin->w_wrow
781 if (curwin->w_wcol >= curwin->w_width_inner) {
782 // this same formula is used in validate_cursor_col()
783 n = (curwin->w_wcol - curwin->w_width_inner) / width + 1;
784 curwin->w_wcol -= n * width;
785 curwin->w_wrow += n;
786
787 /* When cursor wraps to first char of next line in Insert
788 * mode, the 'showbreak' string isn't shown, backup to first
789 * column */
790 if (*p_sbr && *get_cursor_pos_ptr() == NUL
791 && curwin->w_wcol == (int)vim_strsize(p_sbr))
792 curwin->w_wcol = 0;
793 }
794 }
795 /* No line wrapping: compute curwin->w_leftcol if scrolling is on and line
796 * is not folded.
797 * If scrolling is off, curwin->w_leftcol is assumed to be 0 */
798 else if (may_scroll
799 && !curwin->w_cline_folded
800 ) {
801 /*
802 * If Cursor is left of the screen, scroll rightwards.
803 * If Cursor is right of the screen, scroll leftwards
804 * If we get closer to the edge than 'sidescrolloff', scroll a little
805 * extra
806 */
807 assert(p_siso <= INT_MAX);
808 int off_left = startcol - curwin->w_leftcol - (int)p_siso;
809 int off_right =
810 endcol - curwin->w_leftcol - curwin->w_width_inner + (int)p_siso + 1;
811 if (off_left < 0 || off_right > 0) {
812 int diff = (off_left < 0) ? -off_left: off_right;
813
814 /* When far off or not enough room on either side, put cursor in
815 * middle of window. */
816 int new_leftcol;
817 if (p_ss == 0 || diff >= textwidth / 2 || off_right >= off_left)
818 new_leftcol = curwin->w_wcol - extra - textwidth / 2;
819 else {
820 if (diff < p_ss) {
821 assert(p_ss <= INT_MAX);
822 diff = (int)p_ss;
823 }
824 if (off_left < 0)
825 new_leftcol = curwin->w_leftcol - diff;
826 else
827 new_leftcol = curwin->w_leftcol + diff;
828 }
829 if (new_leftcol < 0)
830 new_leftcol = 0;
831 if (new_leftcol != (int)curwin->w_leftcol) {
832 curwin->w_leftcol = new_leftcol;
833 win_check_anchored_floats(curwin);
834 // screen has to be redrawn with new curwin->w_leftcol
835 redraw_later(NOT_VALID);
836 }
837 }
838 curwin->w_wcol -= curwin->w_leftcol;
839 } else if (curwin->w_wcol > (int)curwin->w_leftcol)
840 curwin->w_wcol -= curwin->w_leftcol;
841 else
842 curwin->w_wcol = 0;
843
844 /* Skip over filler lines. At the top use w_topfill, there
845 * may be some filler lines above the window. */
846 if (curwin->w_cursor.lnum == curwin->w_topline)
847 curwin->w_wrow += curwin->w_topfill;
848 else
849 curwin->w_wrow += diff_check_fill(curwin, curwin->w_cursor.lnum);
850
851 prev_skipcol = curwin->w_skipcol;
852
853 int plines = 0;
854 if ((curwin->w_wrow >= curwin->w_height_inner
855 || ((prev_skipcol > 0
856 || curwin->w_wrow + p_so >= curwin->w_height_inner)
857 && (plines =
858 plines_win_nofill(curwin, curwin->w_cursor.lnum, false)) - 1
859 >= curwin->w_height_inner))
860 && curwin->w_height_inner != 0
861 && curwin->w_cursor.lnum == curwin->w_topline
862 && width > 0
863 && curwin->w_width_inner != 0
864 ) {
865 /* Cursor past end of screen. Happens with a single line that does
866 * not fit on screen. Find a skipcol to show the text around the
867 * cursor. Avoid scrolling all the time. compute value of "extra":
868 * 1: Less than "p_so" lines above
869 * 2: Less than "p_so" lines below
870 * 3: both of them */
871 extra = 0;
872 if (curwin->w_skipcol + p_so * width > curwin->w_virtcol)
873 extra = 1;
874 /* Compute last display line of the buffer line that we want at the
875 * bottom of the window. */
876 if (plines == 0) {
877 plines = plines_win(curwin, curwin->w_cursor.lnum, false);
878 }
879 plines--;
880 if (plines > curwin->w_wrow + p_so) {
881 assert(p_so <= INT_MAX);
882 n = curwin->w_wrow + (int)p_so;
883 } else {
884 n = plines;
885 }
886 if ((colnr_T)n >= curwin->w_height_inner + curwin->w_skipcol / width) {
887 extra += 2;
888 }
889
890 if (extra == 3 || plines < p_so * 2) {
891 // not enough room for 'scrolloff', put cursor in the middle
892 n = curwin->w_virtcol / width;
893 if (n > curwin->w_height_inner / 2) {
894 n -= curwin->w_height_inner / 2;
895 } else {
896 n = 0;
897 }
898 // don't skip more than necessary
899 if (n > plines - curwin->w_height_inner + 1) {
900 n = plines - curwin->w_height_inner + 1;
901 }
902 curwin->w_skipcol = n * width;
903 } else if (extra == 1) {
904 /* less then 'scrolloff' lines above, decrease skipcol */
905 assert(p_so <= INT_MAX);
906 extra = (curwin->w_skipcol + (int)p_so * width - curwin->w_virtcol
907 + width - 1) / width;
908 if (extra > 0) {
909 if ((colnr_T)(extra * width) > curwin->w_skipcol)
910 extra = curwin->w_skipcol / width;
911 curwin->w_skipcol -= extra * width;
912 }
913 } else if (extra == 2) {
914 // less then 'scrolloff' lines below, increase skipcol
915 endcol = (n - curwin->w_height_inner + 1) * width;
916 while (endcol > curwin->w_virtcol) {
917 endcol -= width;
918 }
919 if (endcol > curwin->w_skipcol) {
920 curwin->w_skipcol = endcol;
921 }
922 }
923
924 curwin->w_wrow -= curwin->w_skipcol / width;
925 if (curwin->w_wrow >= curwin->w_height_inner) {
926 // small window, make sure cursor is in it
927 extra = curwin->w_wrow - curwin->w_height_inner + 1;
928 curwin->w_skipcol += extra * width;
929 curwin->w_wrow -= extra;
930 }
931
932 // extra could be either positive or negative
933 extra = ((int)prev_skipcol - (int)curwin->w_skipcol) / width;
934 win_scroll_lines(curwin, 0, extra);
935 } else {
936 curwin->w_skipcol = 0;
937 }
938 if (prev_skipcol != curwin->w_skipcol)
939 redraw_later(NOT_VALID);
940
941 /* Redraw when w_virtcol changes and 'cursorcolumn' is set */
942 if (curwin->w_p_cuc && (curwin->w_valid & VALID_VIRTCOL) == 0
943 && !pum_visible()) {
944 redraw_later(SOME_VALID);
945 }
946
947 curwin->w_valid |= VALID_WCOL|VALID_WROW|VALID_VIRTCOL;
948}
949
950/// Compute the screen position of text character at "pos" in window "wp"
951/// The resulting values are one-based, zero when character is not visible.
952///
953/// @param[out] rowp screen row
954/// @param[out] scolp start screen column
955/// @param[out] ccolp cursor screen column
956/// @param[out] ecolp end screen column
957void textpos2screenpos(win_T *wp, pos_T *pos, int *rowp, int *scolp,
958 int *ccolp, int *ecolp, bool local)
959{
960 colnr_T scol = 0, ccol = 0, ecol = 0;
961 int row = 0;
962 int rowoff = 0;
963 colnr_T coloff = 0;
964 bool visible_row = false;
965
966 if (pos->lnum >= wp->w_topline && pos->lnum < wp->w_botline) {
967 row = plines_m_win(wp, wp->w_topline, pos->lnum - 1) + 1;
968 visible_row = true;
969 } else if (pos->lnum < wp->w_topline) {
970 row = 0;
971 } else {
972 row = wp->w_height_inner;
973 }
974
975 bool existing_row = (pos->lnum > 0
976 && pos->lnum <= wp->w_buffer->b_ml.ml_line_count);
977
978 if ((local && existing_row) || visible_row) {
979 colnr_T off;
980 colnr_T col;
981 int width;
982
983 getvcol(wp, pos, &scol, &ccol, &ecol);
984
985 // similar to what is done in validate_cursor_col()
986 col = scol;
987 off = win_col_off(wp);
988 col += off;
989 width = wp->w_width - off + win_col_off2(wp);
990
991 // long line wrapping, adjust row
992 if (wp->w_p_wrap && col >= (colnr_T)wp->w_width && width > 0) {
993 // use same formula as what is used in curs_columns()
994 rowoff = visible_row ? ((col - wp->w_width) / width + 1) : 0;
995 col -= rowoff * width;
996 }
997
998 col -= wp->w_leftcol;
999
1000 if (col >= 0 && col < width) {
1001 coloff = col - scol + (local ? 0 : wp->w_wincol) + 1;
1002 } else {
1003 scol = ccol = ecol = 0;
1004 // character is left or right of the window
1005 if (local) {
1006 coloff = col < 0 ? -1 : wp->w_width_inner + 1;
1007 } else {
1008 row = 0;
1009 }
1010 }
1011 }
1012 *rowp = (local ? 0 : wp->w_winrow) + row + rowoff;
1013 *scolp = scol + coloff;
1014 *ccolp = ccol + coloff;
1015 *ecolp = ecol + coloff;
1016}
1017
1018/*
1019 * Scroll the current window down by "line_count" logical lines. "CTRL-Y"
1020 */
1021void
1022scrolldown (
1023 long line_count,
1024 int byfold /* true: count a closed fold as one line */
1025)
1026{
1027 int done = 0; /* total # of physical lines done */
1028
1029 /* Make sure w_topline is at the first of a sequence of folded lines. */
1030 (void)hasFolding(curwin->w_topline, &curwin->w_topline, NULL);
1031 validate_cursor(); /* w_wrow needs to be valid */
1032 while (line_count-- > 0) {
1033 if (curwin->w_topfill < diff_check(curwin, curwin->w_topline)
1034 && curwin->w_topfill < curwin->w_height_inner - 1) {
1035 curwin->w_topfill++;
1036 done++;
1037 } else {
1038 if (curwin->w_topline == 1)
1039 break;
1040 --curwin->w_topline;
1041 curwin->w_topfill = 0;
1042 /* A sequence of folded lines only counts for one logical line */
1043 linenr_T first;
1044 if (hasFolding(curwin->w_topline, &first, NULL)) {
1045 ++done;
1046 if (!byfold)
1047 line_count -= curwin->w_topline - first - 1;
1048 curwin->w_botline -= curwin->w_topline - first;
1049 curwin->w_topline = first;
1050 } else
1051 done += plines_nofill(curwin->w_topline);
1052 }
1053 --curwin->w_botline; /* approximate w_botline */
1054 invalidate_botline();
1055 }
1056 curwin->w_wrow += done; /* keep w_wrow updated */
1057 curwin->w_cline_row += done; /* keep w_cline_row updated */
1058
1059 if (curwin->w_cursor.lnum == curwin->w_topline)
1060 curwin->w_cline_row = 0;
1061 check_topfill(curwin, true);
1062
1063 /*
1064 * Compute the row number of the last row of the cursor line
1065 * and move the cursor onto the displayed part of the window.
1066 */
1067 int wrow = curwin->w_wrow;
1068 if (curwin->w_p_wrap
1069 && curwin->w_width_inner != 0
1070 ) {
1071 validate_virtcol();
1072 validate_cheight();
1073 wrow += curwin->w_cline_height - 1 -
1074 curwin->w_virtcol / curwin->w_width_inner;
1075 }
1076 bool moved = false;
1077 while (wrow >= curwin->w_height_inner && curwin->w_cursor.lnum > 1) {
1078 linenr_T first;
1079 if (hasFolding(curwin->w_cursor.lnum, &first, NULL)) {
1080 --wrow;
1081 if (first == 1)
1082 curwin->w_cursor.lnum = 1;
1083 else
1084 curwin->w_cursor.lnum = first - 1;
1085 } else
1086 wrow -= plines(curwin->w_cursor.lnum--);
1087 curwin->w_valid &=
1088 ~(VALID_WROW|VALID_WCOL|VALID_CHEIGHT|VALID_CROW|VALID_VIRTCOL);
1089 moved = true;
1090 }
1091 if (moved) {
1092 /* Move cursor to first line of closed fold. */
1093 foldAdjustCursor();
1094 coladvance(curwin->w_curswant);
1095 }
1096}
1097
1098/*
1099 * Scroll the current window up by "line_count" logical lines. "CTRL-E"
1100 */
1101void
1102scrollup (
1103 long line_count,
1104 int byfold /* true: count a closed fold as one line */
1105)
1106{
1107 if ((byfold && hasAnyFolding(curwin))
1108 || curwin->w_p_diff) {
1109 // count each sequence of folded lines as one logical line
1110 linenr_T lnum = curwin->w_topline;
1111 while (line_count--) {
1112 if (curwin->w_topfill > 0)
1113 --curwin->w_topfill;
1114 else {
1115 if (byfold)
1116 (void)hasFolding(lnum, NULL, &lnum);
1117 if (lnum >= curbuf->b_ml.ml_line_count)
1118 break;
1119 ++lnum;
1120 curwin->w_topfill = diff_check_fill(curwin, lnum);
1121 }
1122 }
1123 /* approximate w_botline */
1124 curwin->w_botline += lnum - curwin->w_topline;
1125 curwin->w_topline = lnum;
1126 } else {
1127 curwin->w_topline += line_count;
1128 curwin->w_botline += line_count; /* approximate w_botline */
1129 }
1130
1131 if (curwin->w_topline > curbuf->b_ml.ml_line_count)
1132 curwin->w_topline = curbuf->b_ml.ml_line_count;
1133 if (curwin->w_botline > curbuf->b_ml.ml_line_count + 1)
1134 curwin->w_botline = curbuf->b_ml.ml_line_count + 1;
1135
1136 check_topfill(curwin, false);
1137
1138 if (hasAnyFolding(curwin))
1139 /* Make sure w_topline is at the first of a sequence of folded lines. */
1140 (void)hasFolding(curwin->w_topline, &curwin->w_topline, NULL);
1141
1142 curwin->w_valid &= ~(VALID_WROW|VALID_CROW|VALID_BOTLINE);
1143 if (curwin->w_cursor.lnum < curwin->w_topline) {
1144 curwin->w_cursor.lnum = curwin->w_topline;
1145 curwin->w_valid &=
1146 ~(VALID_WROW|VALID_WCOL|VALID_CHEIGHT|VALID_CROW|VALID_VIRTCOL);
1147 coladvance(curwin->w_curswant);
1148 }
1149}
1150
1151/*
1152 * Don't end up with too many filler lines in the window.
1153 */
1154void
1155check_topfill (
1156 win_T *wp,
1157 bool down /* when true scroll down when not enough space */
1158)
1159{
1160 if (wp->w_topfill > 0) {
1161 int n = plines_win_nofill(wp, wp->w_topline, true);
1162 if (wp->w_topfill + n > wp->w_height_inner) {
1163 if (down && wp->w_topline > 1) {
1164 --wp->w_topline;
1165 wp->w_topfill = 0;
1166 } else {
1167 wp->w_topfill = wp->w_height_inner - n;
1168 if (wp->w_topfill < 0) {
1169 wp->w_topfill = 0;
1170 }
1171 }
1172 }
1173 }
1174 win_check_anchored_floats(curwin);
1175}
1176
1177/*
1178 * Use as many filler lines as possible for w_topline. Make sure w_topline
1179 * is still visible.
1180 */
1181static void max_topfill(void)
1182{
1183 int n = plines_nofill(curwin->w_topline);
1184 if (n >= curwin->w_height_inner) {
1185 curwin->w_topfill = 0;
1186 } else {
1187 curwin->w_topfill = diff_check_fill(curwin, curwin->w_topline);
1188 if (curwin->w_topfill + n > curwin->w_height_inner) {
1189 curwin->w_topfill = curwin->w_height_inner - n;
1190 }
1191 }
1192}
1193
1194/*
1195 * Scroll the screen one line down, but don't do it if it would move the
1196 * cursor off the screen.
1197 */
1198void scrolldown_clamp(void)
1199{
1200 int can_fill = (curwin->w_topfill
1201 < diff_check_fill(curwin, curwin->w_topline));
1202
1203 if (curwin->w_topline <= 1
1204 && !can_fill
1205 )
1206 return;
1207
1208 validate_cursor(); /* w_wrow needs to be valid */
1209
1210 /*
1211 * Compute the row number of the last row of the cursor line
1212 * and make sure it doesn't go off the screen. Make sure the cursor
1213 * doesn't go past 'scrolloff' lines from the screen end.
1214 */
1215 int end_row = curwin->w_wrow;
1216 if (can_fill)
1217 ++end_row;
1218 else
1219 end_row += plines_nofill(curwin->w_topline - 1);
1220 if (curwin->w_p_wrap
1221 && curwin->w_width_inner != 0
1222 ) {
1223 validate_cheight();
1224 validate_virtcol();
1225 end_row += curwin->w_cline_height - 1 -
1226 curwin->w_virtcol / curwin->w_width_inner;
1227 }
1228 if (end_row < curwin->w_height_inner - p_so) {
1229 if (can_fill) {
1230 ++curwin->w_topfill;
1231 check_topfill(curwin, true);
1232 } else {
1233 --curwin->w_topline;
1234 curwin->w_topfill = 0;
1235 }
1236 (void)hasFolding(curwin->w_topline, &curwin->w_topline, NULL);
1237 --curwin->w_botline; /* approximate w_botline */
1238 curwin->w_valid &= ~(VALID_WROW|VALID_CROW|VALID_BOTLINE);
1239 }
1240}
1241
1242/*
1243 * Scroll the screen one line up, but don't do it if it would move the cursor
1244 * off the screen.
1245 */
1246void scrollup_clamp(void)
1247{
1248 if (curwin->w_topline == curbuf->b_ml.ml_line_count
1249 && curwin->w_topfill == 0
1250 )
1251 return;
1252
1253 validate_cursor(); /* w_wrow needs to be valid */
1254
1255 /*
1256 * Compute the row number of the first row of the cursor line
1257 * and make sure it doesn't go off the screen. Make sure the cursor
1258 * doesn't go before 'scrolloff' lines from the screen start.
1259 */
1260 int start_row = curwin->w_wrow - plines_nofill(curwin->w_topline)
1261 - curwin->w_topfill;
1262 if (curwin->w_p_wrap
1263 && curwin->w_width_inner != 0
1264 ) {
1265 validate_virtcol();
1266 start_row -= curwin->w_virtcol / curwin->w_width_inner;
1267 }
1268 if (start_row >= p_so) {
1269 if (curwin->w_topfill > 0)
1270 --curwin->w_topfill;
1271 else {
1272 (void)hasFolding(curwin->w_topline, NULL, &curwin->w_topline);
1273 ++curwin->w_topline;
1274 }
1275 ++curwin->w_botline; /* approximate w_botline */
1276 curwin->w_valid &= ~(VALID_WROW|VALID_CROW|VALID_BOTLINE);
1277 }
1278}
1279
1280/*
1281 * Add one line above "lp->lnum". This can be a filler line, a closed fold or
1282 * a (wrapped) text line. Uses and sets "lp->fill".
1283 * Returns the height of the added line in "lp->height".
1284 * Lines above the first one are incredibly high: MAXCOL.
1285 */
1286static void topline_back(lineoff_T *lp)
1287{
1288 if (lp->fill < diff_check_fill(curwin, lp->lnum)) {
1289 /* Add a filler line. */
1290 ++lp->fill;
1291 lp->height = 1;
1292 } else {
1293 --lp->lnum;
1294 lp->fill = 0;
1295 if (lp->lnum < 1)
1296 lp->height = MAXCOL;
1297 else if (hasFolding(lp->lnum, &lp->lnum, NULL))
1298 /* Add a closed fold */
1299 lp->height = 1;
1300 else {
1301 lp->height = plines_nofill(lp->lnum);
1302 }
1303 }
1304}
1305
1306/*
1307 * Add one line below "lp->lnum". This can be a filler line, a closed fold or
1308 * a (wrapped) text line. Uses and sets "lp->fill".
1309 * Returns the height of the added line in "lp->height".
1310 * Lines below the last one are incredibly high.
1311 */
1312static void botline_forw(lineoff_T *lp)
1313{
1314 if (lp->fill < diff_check_fill(curwin, lp->lnum + 1)) {
1315 /* Add a filler line. */
1316 ++lp->fill;
1317 lp->height = 1;
1318 } else {
1319 ++lp->lnum;
1320 lp->fill = 0;
1321 if (lp->lnum > curbuf->b_ml.ml_line_count) {
1322 lp->height = MAXCOL;
1323 } else if (hasFolding(lp->lnum, NULL, &lp->lnum)) {
1324 // Add a closed fold
1325 lp->height = 1;
1326 } else {
1327 lp->height = plines_nofill(lp->lnum);
1328 }
1329 }
1330}
1331
1332/*
1333 * Switch from including filler lines below lp->lnum to including filler
1334 * lines above loff.lnum + 1. This keeps pointing to the same line.
1335 * When there are no filler lines nothing changes.
1336 */
1337static void botline_topline(lineoff_T *lp)
1338{
1339 if (lp->fill > 0) {
1340 ++lp->lnum;
1341 lp->fill = diff_check_fill(curwin, lp->lnum) - lp->fill + 1;
1342 }
1343}
1344
1345/*
1346 * Switch from including filler lines above lp->lnum to including filler
1347 * lines below loff.lnum - 1. This keeps pointing to the same line.
1348 * When there are no filler lines nothing changes.
1349 */
1350static void topline_botline(lineoff_T *lp)
1351{
1352 if (lp->fill > 0) {
1353 lp->fill = diff_check_fill(curwin, lp->lnum) - lp->fill + 1;
1354 --lp->lnum;
1355 }
1356}
1357
1358/*
1359 * Recompute topline to put the cursor at the top of the window.
1360 * Scroll at least "min_scroll" lines.
1361 * If "always" is true, always set topline (for "zt").
1362 */
1363void scroll_cursor_top(int min_scroll, int always)
1364{
1365 int scrolled = 0;
1366 linenr_T top; /* just above displayed lines */
1367 linenr_T bot; /* just below displayed lines */
1368 linenr_T old_topline = curwin->w_topline;
1369 linenr_T old_topfill = curwin->w_topfill;
1370 linenr_T new_topline;
1371 assert(p_so <= INT_MAX);
1372 int off = (int)p_so;
1373
1374 if (mouse_dragging > 0)
1375 off = mouse_dragging - 1;
1376
1377 /*
1378 * Decrease topline until:
1379 * - it has become 1
1380 * - (part of) the cursor line is moved off the screen or
1381 * - moved at least 'scrolljump' lines and
1382 * - at least 'scrolloff' lines above and below the cursor
1383 */
1384 validate_cheight();
1385 int used = curwin->w_cline_height; // includes filler lines above
1386 if (curwin->w_cursor.lnum < curwin->w_topline) {
1387 scrolled = used;
1388 }
1389
1390 if (hasFolding(curwin->w_cursor.lnum, &top, &bot)) {
1391 --top;
1392 ++bot;
1393 } else {
1394 top = curwin->w_cursor.lnum - 1;
1395 bot = curwin->w_cursor.lnum + 1;
1396 }
1397 new_topline = top + 1;
1398
1399 // "used" already contains the number of filler lines above, don't add it
1400 // again.
1401 // Hide filler lines above cursor line by adding them to "extra".
1402 int extra = diff_check_fill(curwin, curwin->w_cursor.lnum);
1403
1404 /*
1405 * Check if the lines from "top" to "bot" fit in the window. If they do,
1406 * set new_topline and advance "top" and "bot" to include more lines.
1407 */
1408 while (top > 0) {
1409 int i = hasFolding(top, &top, NULL)
1410 ? 1 // count one logical line for a sequence of folded lines
1411 : plines_nofill(top);
1412 used += i;
1413 if (extra + i <= off && bot < curbuf->b_ml.ml_line_count) {
1414 if (hasFolding(bot, NULL, &bot))
1415 /* count one logical line for a sequence of folded lines */
1416 ++used;
1417 else
1418 used += plines(bot);
1419 }
1420 if (used > curwin->w_height_inner) {
1421 break;
1422 }
1423 if (top < curwin->w_topline) {
1424 scrolled += i;
1425 }
1426
1427 /*
1428 * If scrolling is needed, scroll at least 'sj' lines.
1429 */
1430 if ((new_topline >= curwin->w_topline || scrolled > min_scroll)
1431 && extra >= off)
1432 break;
1433
1434 extra += i;
1435 new_topline = top;
1436 --top;
1437 ++bot;
1438 }
1439
1440 /*
1441 * If we don't have enough space, put cursor in the middle.
1442 * This makes sure we get the same position when using "k" and "j"
1443 * in a small window.
1444 */
1445 if (used > curwin->w_height_inner) {
1446 scroll_cursor_halfway(false);
1447 } else {
1448 /*
1449 * If "always" is false, only adjust topline to a lower value, higher
1450 * value may happen with wrapping lines
1451 */
1452 if (new_topline < curwin->w_topline || always)
1453 curwin->w_topline = new_topline;
1454 if (curwin->w_topline > curwin->w_cursor.lnum)
1455 curwin->w_topline = curwin->w_cursor.lnum;
1456 curwin->w_topfill = diff_check_fill(curwin, curwin->w_topline);
1457 if (curwin->w_topfill > 0 && extra > off) {
1458 curwin->w_topfill -= extra - off;
1459 if (curwin->w_topfill < 0)
1460 curwin->w_topfill = 0;
1461 }
1462 check_topfill(curwin, false);
1463 if (curwin->w_topline != old_topline
1464 || curwin->w_topfill != old_topfill
1465 )
1466 curwin->w_valid &=
1467 ~(VALID_WROW|VALID_CROW|VALID_BOTLINE|VALID_BOTLINE_AP);
1468 curwin->w_valid |= VALID_TOPLINE;
1469 }
1470}
1471
1472/*
1473 * Set w_empty_rows and w_filler_rows for window "wp", having used up "used"
1474 * screen lines for text lines.
1475 */
1476void set_empty_rows(win_T *wp, int used)
1477{
1478 wp->w_filler_rows = 0;
1479 if (used == 0) {
1480 wp->w_empty_rows = 0; // single line that doesn't fit
1481 } else {
1482 wp->w_empty_rows = wp->w_height_inner - used;
1483 if (wp->w_botline <= wp->w_buffer->b_ml.ml_line_count) {
1484 wp->w_filler_rows = diff_check_fill(wp, wp->w_botline);
1485 if (wp->w_empty_rows > wp->w_filler_rows)
1486 wp->w_empty_rows -= wp->w_filler_rows;
1487 else {
1488 wp->w_filler_rows = wp->w_empty_rows;
1489 wp->w_empty_rows = 0;
1490 }
1491 }
1492 }
1493}
1494
1495/*
1496 * Recompute topline to put the cursor at the bottom of the window.
1497 * Scroll at least "min_scroll" lines.
1498 * If "set_topbot" is true, set topline and botline first (for "zb").
1499 * This is messy stuff!!!
1500 */
1501void scroll_cursor_bot(int min_scroll, int set_topbot)
1502{
1503 int used;
1504 int scrolled = 0;
1505 int extra = 0;
1506 lineoff_T loff;
1507 lineoff_T boff;
1508 int fill_below_window;
1509 linenr_T old_topline = curwin->w_topline;
1510 int old_topfill = curwin->w_topfill;
1511 linenr_T old_botline = curwin->w_botline;
1512 int old_valid = curwin->w_valid;
1513 int old_empty_rows = curwin->w_empty_rows;
1514 linenr_T cln = curwin->w_cursor.lnum; /* Cursor Line Number */
1515
1516 if (set_topbot) {
1517 used = 0;
1518 curwin->w_botline = cln + 1;
1519 loff.fill = 0;
1520 for (curwin->w_topline = curwin->w_botline;
1521 curwin->w_topline > 1;
1522 curwin->w_topline = loff.lnum) {
1523 loff.lnum = curwin->w_topline;
1524 topline_back(&loff);
1525 if (loff.height == MAXCOL
1526 || used + loff.height > curwin->w_height_inner) {
1527 break;
1528 }
1529 used += loff.height;
1530 curwin->w_topfill = loff.fill;
1531 }
1532 set_empty_rows(curwin, used);
1533 curwin->w_valid |= VALID_BOTLINE|VALID_BOTLINE_AP;
1534 if (curwin->w_topline != old_topline
1535 || curwin->w_topfill != old_topfill
1536 )
1537 curwin->w_valid &= ~(VALID_WROW|VALID_CROW);
1538 } else
1539 validate_botline();
1540
1541 /* The lines of the cursor line itself are always used. */
1542 used = plines_nofill(cln);
1543
1544 /* If the cursor is below botline, we will at least scroll by the height
1545 * of the cursor line. Correct for empty lines, which are really part of
1546 * botline. */
1547 if (cln >= curwin->w_botline) {
1548 scrolled = used;
1549 if (cln == curwin->w_botline)
1550 scrolled -= curwin->w_empty_rows;
1551 }
1552
1553 /*
1554 * Stop counting lines to scroll when
1555 * - hitting start of the file
1556 * - scrolled nothing or at least 'sj' lines
1557 * - at least 'so' lines below the cursor
1558 * - lines between botline and cursor have been counted
1559 */
1560 if (!hasFolding(curwin->w_cursor.lnum, &loff.lnum, &boff.lnum)) {
1561 loff.lnum = cln;
1562 boff.lnum = cln;
1563 }
1564 loff.fill = 0;
1565 boff.fill = 0;
1566 fill_below_window = diff_check_fill(curwin, curwin->w_botline)
1567 - curwin->w_filler_rows;
1568
1569 while (loff.lnum > 1) {
1570 /* Stop when scrolled nothing or at least "min_scroll", found "extra"
1571 * context for 'scrolloff' and counted all lines below the window. */
1572 if ((((scrolled <= 0 || scrolled >= min_scroll)
1573 && extra >= (
1574 mouse_dragging > 0 ? mouse_dragging - 1 :
1575 p_so))
1576 || boff.lnum + 1 > curbuf->b_ml.ml_line_count)
1577 && loff.lnum <= curwin->w_botline
1578 && (loff.lnum < curwin->w_botline
1579 || loff.fill >= fill_below_window)
1580 )
1581 break;
1582
1583 /* Add one line above */
1584 topline_back(&loff);
1585 if (loff.height == MAXCOL) {
1586 used = MAXCOL;
1587 } else {
1588 used += loff.height;
1589 }
1590 if (used > curwin->w_height_inner) {
1591 break;
1592 }
1593 if (loff.lnum >= curwin->w_botline
1594 && (loff.lnum > curwin->w_botline
1595 || loff.fill <= fill_below_window)
1596 ) {
1597 /* Count screen lines that are below the window. */
1598 scrolled += loff.height;
1599 if (loff.lnum == curwin->w_botline
1600 && loff.fill == 0) {
1601 scrolled -= curwin->w_empty_rows;
1602 }
1603 }
1604
1605 if (boff.lnum < curbuf->b_ml.ml_line_count) {
1606 /* Add one line below */
1607 botline_forw(&boff);
1608 used += boff.height;
1609 if (used > curwin->w_height_inner) {
1610 break;
1611 }
1612 if (extra < (
1613 mouse_dragging > 0 ? mouse_dragging - 1 :
1614 p_so) || scrolled < min_scroll) {
1615 extra += boff.height;
1616 if (boff.lnum >= curwin->w_botline
1617 || (boff.lnum + 1 == curwin->w_botline
1618 && boff.fill > curwin->w_filler_rows)
1619 ) {
1620 /* Count screen lines that are below the window. */
1621 scrolled += boff.height;
1622 if (boff.lnum == curwin->w_botline
1623 && boff.fill == 0
1624 )
1625 scrolled -= curwin->w_empty_rows;
1626 }
1627 }
1628 }
1629 }
1630
1631 linenr_T line_count;
1632 // curwin->w_empty_rows is larger, no need to scroll
1633 if (scrolled <= 0) {
1634 line_count = 0;
1635 // more than a screenfull, don't scroll but redraw
1636 } else if (used > curwin->w_height_inner) {
1637 line_count = used;
1638 // scroll minimal number of lines
1639 } else {
1640 line_count = 0;
1641 boff.fill = curwin->w_topfill;
1642 boff.lnum = curwin->w_topline - 1;
1643 int i;
1644 for (i = 0; i < scrolled && boff.lnum < curwin->w_botline; ) {
1645 botline_forw(&boff);
1646 i += boff.height;
1647 ++line_count;
1648 }
1649 if (i < scrolled) /* below curwin->w_botline, don't scroll */
1650 line_count = 9999;
1651 }
1652
1653 /*
1654 * Scroll up if the cursor is off the bottom of the screen a bit.
1655 * Otherwise put it at 1/2 of the screen.
1656 */
1657 if (line_count >= curwin->w_height_inner && line_count > min_scroll) {
1658 scroll_cursor_halfway(false);
1659 } else {
1660 scrollup(line_count, true);
1661 }
1662
1663 /*
1664 * If topline didn't change we need to restore w_botline and w_empty_rows
1665 * (we changed them).
1666 * If topline did change, update_screen() will set botline.
1667 */
1668 if (curwin->w_topline == old_topline && set_topbot) {
1669 curwin->w_botline = old_botline;
1670 curwin->w_empty_rows = old_empty_rows;
1671 curwin->w_valid = old_valid;
1672 }
1673 curwin->w_valid |= VALID_TOPLINE;
1674}
1675
1676/// Recompute topline to put the cursor halfway across the window
1677///
1678/// @param atend if true, also put the cursor halfway to the end of the file.
1679///
1680void scroll_cursor_halfway(int atend)
1681{
1682 int above = 0;
1683 int topfill = 0;
1684 int below = 0;
1685 lineoff_T loff;
1686 lineoff_T boff;
1687 linenr_T old_topline = curwin->w_topline;
1688
1689 loff.lnum = boff.lnum = curwin->w_cursor.lnum;
1690 (void)hasFolding(loff.lnum, &loff.lnum, &boff.lnum);
1691 int used = plines_nofill(loff.lnum);
1692 loff.fill = 0;
1693 boff.fill = 0;
1694 linenr_T topline = loff.lnum;
1695 while (topline > 1) {
1696 if (below <= above) { /* add a line below the cursor first */
1697 if (boff.lnum < curbuf->b_ml.ml_line_count) {
1698 botline_forw(&boff);
1699 used += boff.height;
1700 if (used > curwin->w_height_inner) {
1701 break;
1702 }
1703 below += boff.height;
1704 } else {
1705 ++below; /* count a "~" line */
1706 if (atend)
1707 ++used;
1708 }
1709 }
1710
1711 if (below > above) { /* add a line above the cursor */
1712 topline_back(&loff);
1713 if (loff.height == MAXCOL)
1714 used = MAXCOL;
1715 else
1716 used += loff.height;
1717 if (used > curwin->w_height_inner) {
1718 break;
1719 }
1720 above += loff.height;
1721 topline = loff.lnum;
1722 topfill = loff.fill;
1723 }
1724 }
1725 if (!hasFolding(topline, &curwin->w_topline, NULL))
1726 curwin->w_topline = topline;
1727 curwin->w_topfill = topfill;
1728 if (old_topline > curwin->w_topline + curwin->w_height_inner) {
1729 curwin->w_botfill = false;
1730 }
1731 check_topfill(curwin, false);
1732 curwin->w_valid &= ~(VALID_WROW|VALID_CROW|VALID_BOTLINE|VALID_BOTLINE_AP);
1733 curwin->w_valid |= VALID_TOPLINE;
1734}
1735
1736/*
1737 * Correct the cursor position so that it is in a part of the screen at least
1738 * 'so' lines from the top and bottom, if possible.
1739 * If not possible, put it at the same position as scroll_cursor_halfway().
1740 * When called topline must be valid!
1741 */
1742void cursor_correct(void)
1743{
1744 /*
1745 * How many lines we would like to have above/below the cursor depends on
1746 * whether the first/last line of the file is on screen.
1747 */
1748 assert(p_so <= INT_MAX);
1749 int above_wanted = (int)p_so;
1750 int below_wanted = (int)p_so;
1751 if (mouse_dragging > 0) {
1752 above_wanted = mouse_dragging - 1;
1753 below_wanted = mouse_dragging - 1;
1754 }
1755 if (curwin->w_topline == 1) {
1756 above_wanted = 0;
1757 int max_off = curwin->w_height_inner / 2;
1758 if (below_wanted > max_off) {
1759 below_wanted = max_off;
1760 }
1761 }
1762 validate_botline();
1763 if (curwin->w_botline == curbuf->b_ml.ml_line_count + 1
1764 && mouse_dragging == 0
1765 ) {
1766 below_wanted = 0;
1767 int max_off = (curwin->w_height_inner - 1) / 2;
1768 if (above_wanted > max_off) {
1769 above_wanted = max_off;
1770 }
1771 }
1772
1773 /*
1774 * If there are sufficient file-lines above and below the cursor, we can
1775 * return now.
1776 */
1777 linenr_T cln = curwin->w_cursor.lnum; /* Cursor Line Number */
1778 if (cln >= curwin->w_topline + above_wanted
1779 && cln < curwin->w_botline - below_wanted
1780 && !hasAnyFolding(curwin)
1781 )
1782 return;
1783
1784 /*
1785 * Narrow down the area where the cursor can be put by taking lines from
1786 * the top and the bottom until:
1787 * - the desired context lines are found
1788 * - the lines from the top is past the lines from the bottom
1789 */
1790 linenr_T topline = curwin->w_topline;
1791 linenr_T botline = curwin->w_botline - 1;
1792 /* count filler lines as context */
1793 int above = curwin->w_topfill; /* screen lines above topline */
1794 int below = curwin->w_filler_rows; /* screen lines below botline */
1795 while ((above < above_wanted || below < below_wanted) && topline < botline) {
1796 if (below < below_wanted && (below <= above || above >= above_wanted)) {
1797 if (hasFolding(botline, &botline, NULL))
1798 ++below;
1799 else
1800 below += plines(botline);
1801 --botline;
1802 }
1803 if (above < above_wanted && (above < below || below >= below_wanted)) {
1804 if (hasFolding(topline, NULL, &topline))
1805 ++above;
1806 else
1807 above += plines_nofill(topline);
1808
1809 /* Count filler lines below this line as context. */
1810 if (topline < botline)
1811 above += diff_check_fill(curwin, topline + 1);
1812 ++topline;
1813 }
1814 }
1815 if (topline == botline || botline == 0)
1816 curwin->w_cursor.lnum = topline;
1817 else if (topline > botline)
1818 curwin->w_cursor.lnum = botline;
1819 else {
1820 if (cln < topline && curwin->w_topline > 1) {
1821 curwin->w_cursor.lnum = topline;
1822 curwin->w_valid &=
1823 ~(VALID_WROW|VALID_WCOL|VALID_CHEIGHT|VALID_CROW);
1824 }
1825 if (cln > botline && curwin->w_botline <= curbuf->b_ml.ml_line_count) {
1826 curwin->w_cursor.lnum = botline;
1827 curwin->w_valid &=
1828 ~(VALID_WROW|VALID_WCOL|VALID_CHEIGHT|VALID_CROW);
1829 }
1830 }
1831 curwin->w_valid |= VALID_TOPLINE;
1832}
1833
1834
1835/*
1836 * move screen 'count' pages up or down and update screen
1837 *
1838 * return FAIL for failure, OK otherwise
1839 */
1840int onepage(Direction dir, long count)
1841{
1842 long n;
1843 int retval = OK;
1844 lineoff_T loff;
1845 linenr_T old_topline = curwin->w_topline;
1846
1847 if (curbuf->b_ml.ml_line_count == 1) { /* nothing to do */
1848 beep_flush();
1849 return FAIL;
1850 }
1851
1852 for (; count > 0; --count) {
1853 validate_botline();
1854 /*
1855 * It's an error to move a page up when the first line is already on
1856 * the screen. It's an error to move a page down when the last line
1857 * is on the screen and the topline is 'scrolloff' lines from the
1858 * last line.
1859 */
1860 if (dir == FORWARD
1861 ? ((curwin->w_topline >= curbuf->b_ml.ml_line_count - p_so)
1862 && curwin->w_botline > curbuf->b_ml.ml_line_count)
1863 : (curwin->w_topline == 1
1864 && curwin->w_topfill ==
1865 diff_check_fill(curwin, curwin->w_topline)
1866 )) {
1867 beep_flush();
1868 retval = FAIL;
1869 break;
1870 }
1871
1872 loff.fill = 0;
1873 if (dir == FORWARD) {
1874 if (ONE_WINDOW && p_window > 0 && p_window < Rows - 1) {
1875 /* Vi compatible scrolling */
1876 if (p_window <= 2)
1877 ++curwin->w_topline;
1878 else
1879 curwin->w_topline += p_window - 2;
1880 if (curwin->w_topline > curbuf->b_ml.ml_line_count)
1881 curwin->w_topline = curbuf->b_ml.ml_line_count;
1882 curwin->w_cursor.lnum = curwin->w_topline;
1883 } else if (curwin->w_botline > curbuf->b_ml.ml_line_count) {
1884 /* at end of file */
1885 curwin->w_topline = curbuf->b_ml.ml_line_count;
1886 curwin->w_topfill = 0;
1887 curwin->w_valid &= ~(VALID_WROW|VALID_CROW);
1888 } else {
1889 /* For the overlap, start with the line just below the window
1890 * and go upwards. */
1891 loff.lnum = curwin->w_botline;
1892 loff.fill = diff_check_fill(curwin, loff.lnum)
1893 - curwin->w_filler_rows;
1894 get_scroll_overlap(&loff, -1);
1895 curwin->w_topline = loff.lnum;
1896 curwin->w_topfill = loff.fill;
1897 check_topfill(curwin, false);
1898 curwin->w_cursor.lnum = curwin->w_topline;
1899 curwin->w_valid &= ~(VALID_WCOL|VALID_CHEIGHT|VALID_WROW|
1900 VALID_CROW|VALID_BOTLINE|VALID_BOTLINE_AP);
1901 }
1902 } else { /* dir == BACKWARDS */
1903 if (curwin->w_topline == 1) {
1904 /* Include max number of filler lines */
1905 max_topfill();
1906 continue;
1907 }
1908 if (ONE_WINDOW && p_window > 0 && p_window < Rows - 1) {
1909 /* Vi compatible scrolling (sort of) */
1910 if (p_window <= 2)
1911 --curwin->w_topline;
1912 else
1913 curwin->w_topline -= p_window - 2;
1914 if (curwin->w_topline < 1)
1915 curwin->w_topline = 1;
1916 curwin->w_cursor.lnum = curwin->w_topline + p_window - 1;
1917 if (curwin->w_cursor.lnum > curbuf->b_ml.ml_line_count)
1918 curwin->w_cursor.lnum = curbuf->b_ml.ml_line_count;
1919 continue;
1920 }
1921
1922 /* Find the line at the top of the window that is going to be the
1923 * line at the bottom of the window. Make sure this results in
1924 * the same line as before doing CTRL-F. */
1925 loff.lnum = curwin->w_topline - 1;
1926 loff.fill = diff_check_fill(curwin, loff.lnum + 1)
1927 - curwin->w_topfill;
1928 get_scroll_overlap(&loff, 1);
1929
1930 if (loff.lnum >= curbuf->b_ml.ml_line_count) {
1931 loff.lnum = curbuf->b_ml.ml_line_count;
1932 loff.fill = 0;
1933 } else {
1934 botline_topline(&loff);
1935 }
1936 curwin->w_cursor.lnum = loff.lnum;
1937
1938 /* Find the line just above the new topline to get the right line
1939 * at the bottom of the window. */
1940 n = 0;
1941 while (n <= curwin->w_height_inner && loff.lnum >= 1) {
1942 topline_back(&loff);
1943 if (loff.height == MAXCOL)
1944 n = MAXCOL;
1945 else
1946 n += loff.height;
1947 }
1948 if (loff.lnum < 1) { /* at begin of file */
1949 curwin->w_topline = 1;
1950 max_topfill();
1951 curwin->w_valid &= ~(VALID_WROW|VALID_CROW|VALID_BOTLINE);
1952 } else {
1953 /* Go two lines forward again. */
1954 topline_botline(&loff);
1955 botline_forw(&loff);
1956 botline_forw(&loff);
1957 botline_topline(&loff);
1958 /* We're at the wrong end of a fold now. */
1959 (void)hasFolding(loff.lnum, &loff.lnum, NULL);
1960
1961 /* Always scroll at least one line. Avoid getting stuck on
1962 * very long lines. */
1963 if (loff.lnum >= curwin->w_topline
1964 && (loff.lnum > curwin->w_topline
1965 || loff.fill >= curwin->w_topfill)
1966 ) {
1967 /* First try using the maximum number of filler lines. If
1968 * that's not enough, backup one line. */
1969 loff.fill = curwin->w_topfill;
1970 if (curwin->w_topfill < diff_check_fill(curwin,
1971 curwin->w_topline))
1972 max_topfill();
1973 if (curwin->w_topfill == loff.fill) {
1974 --curwin->w_topline;
1975 curwin->w_topfill = 0;
1976 }
1977 comp_botline(curwin);
1978 curwin->w_cursor.lnum = curwin->w_botline - 1;
1979 curwin->w_valid &=
1980 ~(VALID_WCOL | VALID_CHEIGHT | VALID_WROW | VALID_CROW);
1981 } else {
1982 curwin->w_topline = loff.lnum;
1983 curwin->w_topfill = loff.fill;
1984 check_topfill(curwin, false);
1985 curwin->w_valid &= ~(VALID_WROW|VALID_CROW|VALID_BOTLINE);
1986 }
1987 }
1988 }
1989 }
1990 foldAdjustCursor();
1991 cursor_correct();
1992 check_cursor_col();
1993 if (retval == OK) {
1994 beginline(BL_SOL | BL_FIX);
1995 }
1996 curwin->w_valid &= ~(VALID_WCOL|VALID_WROW|VALID_VIRTCOL);
1997
1998 if (retval == OK && dir == FORWARD) {
1999 // Avoid the screen jumping up and down when 'scrolloff' is non-zero.
2000 // But make sure we scroll at least one line (happens with mix of long
2001 // wrapping lines and non-wrapping line).
2002 if (check_top_offset()) {
2003 scroll_cursor_top(1, false);
2004 if (curwin->w_topline <= old_topline
2005 && old_topline < curbuf->b_ml.ml_line_count) {
2006 curwin->w_topline = old_topline + 1;
2007 (void)hasFolding(curwin->w_topline, &curwin->w_topline, NULL);
2008 }
2009 } else if (curwin->w_botline > curbuf->b_ml.ml_line_count) {
2010 (void)hasFolding(curwin->w_topline, &curwin->w_topline, NULL);
2011 }
2012 }
2013
2014 redraw_later(VALID);
2015 return retval;
2016}
2017
2018/*
2019 * Decide how much overlap to use for page-up or page-down scrolling.
2020 * This is symmetric, so that doing both keeps the same lines displayed.
2021 * Three lines are examined:
2022 *
2023 * before CTRL-F after CTRL-F / before CTRL-B
2024 * etc. l1
2025 * l1 last but one line ------------
2026 * l2 last text line l2 top text line
2027 * ------------- l3 second text line
2028 * l3 etc.
2029 */
2030static void get_scroll_overlap(lineoff_T *lp, int dir)
2031{
2032 int min_height = curwin->w_height_inner - 2;
2033
2034 if (lp->fill > 0)
2035 lp->height = 1;
2036 else
2037 lp->height = plines_nofill(lp->lnum);
2038 int h1 = lp->height;
2039 if (h1 > min_height)
2040 return; /* no overlap */
2041
2042 lineoff_T loff0 = *lp;
2043 if (dir > 0)
2044 botline_forw(lp);
2045 else
2046 topline_back(lp);
2047 int h2 = lp->height;
2048 if (h2 == MAXCOL || h2 + h1 > min_height) {
2049 *lp = loff0; /* no overlap */
2050 return;
2051 }
2052
2053 lineoff_T loff1 = *lp;
2054 if (dir > 0)
2055 botline_forw(lp);
2056 else
2057 topline_back(lp);
2058 int h3 = lp->height;
2059 if (h3 == MAXCOL || h3 + h2 > min_height) {
2060 *lp = loff0; /* no overlap */
2061 return;
2062 }
2063
2064 lineoff_T loff2 = *lp;
2065 if (dir > 0)
2066 botline_forw(lp);
2067 else
2068 topline_back(lp);
2069 int h4 = lp->height;
2070 if (h4 == MAXCOL || h4 + h3 + h2 > min_height || h3 + h2 + h1 > min_height)
2071 *lp = loff1; /* 1 line overlap */
2072 else
2073 *lp = loff2; /* 2 lines overlap */
2074 return;
2075}
2076
2077// Scroll 'scroll' lines up or down.
2078void halfpage(bool flag, linenr_T Prenum)
2079{
2080 long scrolled = 0;
2081 int i;
2082
2083 if (Prenum) {
2084 curwin->w_p_scr = (Prenum > curwin->w_height_inner) ? curwin->w_height_inner
2085 : Prenum;
2086 }
2087 assert(curwin->w_p_scr <= INT_MAX);
2088 int n = curwin->w_p_scr <= curwin->w_height_inner ? (int)curwin->w_p_scr
2089 : curwin->w_height_inner;
2090
2091 update_topline();
2092 validate_botline();
2093 int room = curwin->w_empty_rows + curwin->w_filler_rows;
2094 if (flag) {
2095 /*
2096 * scroll the text up
2097 */
2098 while (n > 0 && curwin->w_botline <= curbuf->b_ml.ml_line_count) {
2099 if (curwin->w_topfill > 0) {
2100 i = 1;
2101 n--;
2102 curwin->w_topfill--;
2103 } else {
2104 i = plines_nofill(curwin->w_topline);
2105 n -= i;
2106 if (n < 0 && scrolled > 0)
2107 break;
2108 (void)hasFolding(curwin->w_topline, NULL, &curwin->w_topline);
2109 ++curwin->w_topline;
2110 curwin->w_topfill = diff_check_fill(curwin, curwin->w_topline);
2111
2112 if (curwin->w_cursor.lnum < curbuf->b_ml.ml_line_count) {
2113 ++curwin->w_cursor.lnum;
2114 curwin->w_valid &=
2115 ~(VALID_VIRTCOL|VALID_CHEIGHT|VALID_WCOL);
2116 }
2117 }
2118 curwin->w_valid &= ~(VALID_CROW|VALID_WROW);
2119 scrolled += i;
2120
2121 /*
2122 * Correct w_botline for changed w_topline.
2123 * Won't work when there are filler lines.
2124 */
2125 if (curwin->w_p_diff)
2126 curwin->w_valid &= ~(VALID_BOTLINE|VALID_BOTLINE_AP);
2127 else {
2128 room += i;
2129 do {
2130 i = plines(curwin->w_botline);
2131 if (i > room)
2132 break;
2133 (void)hasFolding(curwin->w_botline, NULL,
2134 &curwin->w_botline);
2135 ++curwin->w_botline;
2136 room -= i;
2137 } while (curwin->w_botline <= curbuf->b_ml.ml_line_count);
2138 }
2139 }
2140
2141 // When hit bottom of the file: move cursor down.
2142 if (n > 0) {
2143 if (hasAnyFolding(curwin)) {
2144 while (--n >= 0
2145 && curwin->w_cursor.lnum < curbuf->b_ml.ml_line_count) {
2146 (void)hasFolding(curwin->w_cursor.lnum, NULL,
2147 &curwin->w_cursor.lnum);
2148 ++curwin->w_cursor.lnum;
2149 }
2150 } else
2151 curwin->w_cursor.lnum += n;
2152 check_cursor_lnum();
2153 }
2154 } else {
2155 /*
2156 * scroll the text down
2157 */
2158 while (n > 0 && curwin->w_topline > 1) {
2159 if (curwin->w_topfill < diff_check_fill(curwin, curwin->w_topline)) {
2160 i = 1;
2161 n--;
2162 curwin->w_topfill++;
2163 } else {
2164 i = plines_nofill(curwin->w_topline - 1);
2165 n -= i;
2166 if (n < 0 && scrolled > 0)
2167 break;
2168 --curwin->w_topline;
2169 (void)hasFolding(curwin->w_topline, &curwin->w_topline, NULL);
2170 curwin->w_topfill = 0;
2171 }
2172 curwin->w_valid &= ~(VALID_CROW|VALID_WROW|
2173 VALID_BOTLINE|VALID_BOTLINE_AP);
2174 scrolled += i;
2175 if (curwin->w_cursor.lnum > 1) {
2176 --curwin->w_cursor.lnum;
2177 curwin->w_valid &= ~(VALID_VIRTCOL|VALID_CHEIGHT|VALID_WCOL);
2178 }
2179 }
2180
2181 // When hit top of the file: move cursor up.
2182 if (n > 0) {
2183 if (curwin->w_cursor.lnum <= (linenr_T)n)
2184 curwin->w_cursor.lnum = 1;
2185 else if (hasAnyFolding(curwin)) {
2186 while (--n >= 0 && curwin->w_cursor.lnum > 1) {
2187 --curwin->w_cursor.lnum;
2188 (void)hasFolding(curwin->w_cursor.lnum,
2189 &curwin->w_cursor.lnum, NULL);
2190 }
2191 } else
2192 curwin->w_cursor.lnum -= n;
2193 }
2194 }
2195 /* Move cursor to first line of closed fold. */
2196 foldAdjustCursor();
2197 check_topfill(curwin, !flag);
2198 cursor_correct();
2199 beginline(BL_SOL | BL_FIX);
2200 redraw_later(VALID);
2201}
2202
2203void do_check_cursorbind(void)
2204{
2205 linenr_T line = curwin->w_cursor.lnum;
2206 colnr_T col = curwin->w_cursor.col;
2207 colnr_T coladd = curwin->w_cursor.coladd;
2208 colnr_T curswant = curwin->w_curswant;
2209 int set_curswant = curwin->w_set_curswant;
2210 win_T *old_curwin = curwin;
2211 buf_T *old_curbuf = curbuf;
2212 int old_VIsual_select = VIsual_select;
2213 int old_VIsual_active = VIsual_active;
2214
2215 /*
2216 * loop through the cursorbound windows
2217 */
2218 VIsual_select = VIsual_active = 0;
2219 FOR_ALL_WINDOWS_IN_TAB(wp, curtab) {
2220 curwin = wp;
2221 curbuf = curwin->w_buffer;
2222 /* skip original window and windows with 'noscrollbind' */
2223 if (curwin != old_curwin && curwin->w_p_crb) {
2224 if (curwin->w_p_diff) {
2225 curwin->w_cursor.lnum =
2226 diff_get_corresponding_line(old_curbuf, line);
2227 } else {
2228 curwin->w_cursor.lnum = line;
2229 }
2230 curwin->w_cursor.col = col;
2231 curwin->w_cursor.coladd = coladd;
2232 curwin->w_curswant = curswant;
2233 curwin->w_set_curswant = set_curswant;
2234
2235 /* Make sure the cursor is in a valid position. Temporarily set
2236 * "restart_edit" to allow the cursor to be beyond the EOL. */
2237 {
2238 int restart_edit_save = restart_edit;
2239 restart_edit = true;
2240 check_cursor();
2241 if (win_cursorline_standout(curwin) || curwin->w_p_cuc) {
2242 validate_cursor();
2243 }
2244 restart_edit = restart_edit_save;
2245 }
2246 // Correct cursor for multi-byte character.
2247 mb_adjust_cursor();
2248 redraw_later(VALID);
2249
2250 // Only scroll when 'scrollbind' hasn't done this.
2251 if (!curwin->w_p_scb) {
2252 update_topline();
2253 }
2254 curwin->w_redr_status = true;
2255 }
2256 }
2257
2258 /*
2259 * reset current-window
2260 */
2261 VIsual_select = old_VIsual_select;
2262 VIsual_active = old_VIsual_active;
2263 curwin = old_curwin;
2264 curbuf = old_curbuf;
2265}
2266
2267