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 | |
35 | typedef 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 | */ |
50 | static 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 | |
102 | void 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. |
108 | void 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 | */ |
136 | void 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 | */ |
146 | void 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 | */ |
340 | void 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 | */ |
353 | static 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 | */ |
364 | static 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 | |
389 | void 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 | */ |
401 | void 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 | */ |
424 | void changed_window_setting(void) |
425 | { |
426 | changed_window_setting_win(curwin); |
427 | } |
428 | |
429 | void 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 | */ |
440 | void 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 | */ |
459 | void changed_cline_bef_curs(void) |
460 | { |
461 | curwin->w_valid &= ~(VALID_WROW|VALID_WCOL|VALID_VIRTCOL |
462 | |VALID_CHEIGHT|VALID_TOPLINE); |
463 | } |
464 | |
465 | void 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 | */ |
476 | void 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 | |
482 | void 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 | */ |
491 | void 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 | */ |
500 | void invalidate_botline(void) |
501 | { |
502 | curwin->w_valid &= ~(VALID_BOTLINE|VALID_BOTLINE_AP); |
503 | } |
504 | |
505 | void invalidate_botline_win(win_T *wp) |
506 | { |
507 | wp->w_valid &= ~(VALID_BOTLINE|VALID_BOTLINE_AP); |
508 | } |
509 | |
510 | void 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 | */ |
518 | int 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 | */ |
529 | void 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 | */ |
540 | static 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 | */ |
621 | void validate_virtcol(void) |
622 | { |
623 | validate_virtcol_win(curwin); |
624 | } |
625 | |
626 | /* |
627 | * Validate wp->w_virtcol only. |
628 | */ |
629 | void 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 | */ |
645 | static 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 | */ |
662 | void 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 | */ |
692 | int 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 | |
700 | int 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 | */ |
710 | int 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 | |
717 | int 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 | */ |
727 | void 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 = 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 |
957 | void 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 | */ |
1021 | void |
1022 | scrolldown ( |
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 | */ |
1101 | void |
1102 | scrollup ( |
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 | */ |
1154 | void |
1155 | check_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 | */ |
1181 | static 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 | */ |
1198 | void 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 | */ |
1246 | void 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 | */ |
1286 | static 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 | */ |
1312 | static 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 | */ |
1337 | static 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 | */ |
1350 | static 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 | */ |
1363 | void 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 = 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 | */ |
1476 | void 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 | */ |
1501 | void scroll_cursor_bot(int min_scroll, int set_topbot) |
1502 | { |
1503 | int used; |
1504 | int scrolled = 0; |
1505 | int = 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 | /// |
1680 | void 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 | */ |
1742 | void 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 | */ |
1840 | int 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 | */ |
2030 | static 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. |
2078 | void 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 | |
2203 | void 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 | |