diff options
-rw-r--r-- | runtime/doc/autocmd.txt | 3 | ||||
-rw-r--r-- | src/nvim/auevents.lua | 2 | ||||
-rw-r--r-- | src/nvim/buffer_defs.h | 9 | ||||
-rw-r--r-- | src/nvim/edit.c | 6 | ||||
-rw-r--r-- | src/nvim/move.c | 14 | ||||
-rw-r--r-- | src/nvim/normal.c | 23 | ||||
-rw-r--r-- | src/nvim/window.c | 24 | ||||
-rw-r--r-- | test/functional/autocmd/winscrolled_spec.lua | 63 |
8 files changed, 138 insertions, 6 deletions
diff --git a/runtime/doc/autocmd.txt b/runtime/doc/autocmd.txt index a728593c40..66d15d446a 100644 --- a/runtime/doc/autocmd.txt +++ b/runtime/doc/autocmd.txt @@ -1011,6 +1011,9 @@ WinLeave Before leaving a window. If the window to be WinNew When a new window was created. Not done for the first window, when Vim has just started. Before WinEnter. + *WinScrolled* +WinScrolled After scrolling the viewport. + ============================================================================== 6. Patterns *autocmd-pattern* *{pat}* diff --git a/src/nvim/auevents.lua b/src/nvim/auevents.lua index 10647c01a4..9c28398f5b 100644 --- a/src/nvim/auevents.lua +++ b/src/nvim/auevents.lua @@ -113,6 +113,7 @@ return { 'WinEnter', -- after entering a window 'WinLeave', -- before leaving a window 'WinNew', -- when entering a new window + 'WinScrolled', -- after scrolling a window }, aliases = { BufCreate = 'BufAdd', @@ -133,5 +134,6 @@ return { UIEnter=true, UILeave=true, WinClosed=true, + WinScrolled=true, }, } diff --git a/src/nvim/buffer_defs.h b/src/nvim/buffer_defs.h index 4efc341875..540542f409 100644 --- a/src/nvim/buffer_defs.h +++ b/src/nvim/buffer_defs.h @@ -1230,6 +1230,15 @@ struct window_S { colnr_T w_skipcol; // starting column when a single line // doesn't fit in the window + /* + * "w_last_topline" and "w_last_leftcol" are used to determine if + * a Scroll autocommand should be emitted. + */ + linenr_T w_last_topline; ///< last known value for topline + colnr_T w_last_leftcol; ///< last known value for leftcol + int w_last_width; ///< last known value for width + int w_last_height; ///< last known value for height + // // Layout of the window in the screen. // May need to add "msg_scrolled" to "w_winrow" in rare situations. diff --git a/src/nvim/edit.c b/src/nvim/edit.c index 3e62ed9036..d7cca9ba36 100644 --- a/src/nvim/edit.c +++ b/src/nvim/edit.c @@ -1482,6 +1482,12 @@ static void ins_redraw( } } + // Trigger Scroll if viewport changed. + if (ready && has_event(EVENT_WINSCROLLED) + && win_did_scroll(curwin)) { + do_autocmd_winscrolled(curwin); + } + if (curwin->w_p_cole > 0 && conceal_cursor_line(curwin) && conceal_cursor_moved) { redrawWinline(curwin, curwin->w_cursor.lnum); diff --git a/src/nvim/move.c b/src/nvim/move.c index 98a7792a09..218dcd289d 100644 --- a/src/nvim/move.c +++ b/src/nvim/move.c @@ -1023,7 +1023,7 @@ void textpos2screenpos(win_T *wp, pos_T *pos, int *rowp, int *scolp, /* * Scroll the current window down by "line_count" logical lines. "CTRL-Y" */ -void +bool scrolldown ( long line_count, int byfold /* true: count a closed fold as one line */ @@ -1098,17 +1098,21 @@ scrolldown ( foldAdjustCursor(); coladvance(curwin->w_curswant); } + return moved; } /* * Scroll the current window up by "line_count" logical lines. "CTRL-E" */ -void +bool scrollup ( long line_count, int byfold /* true: count a closed fold as one line */ ) { + linenr_T topline = curwin->w_topline; + linenr_T botline = curwin->w_botline; + if ((byfold && hasAnyFolding(curwin)) || curwin->w_p_diff) { // count each sequence of folded lines as one logical line @@ -1151,6 +1155,12 @@ scrollup ( ~(VALID_WROW|VALID_WCOL|VALID_CHEIGHT|VALID_CROW|VALID_VIRTCOL); coladvance(curwin->w_curswant); } + + bool moved = + topline != curwin->w_topline || + botline != curwin->w_botline; + + return moved; } /* diff --git a/src/nvim/normal.c b/src/nvim/normal.c index 11e8d354e4..b09b99cf35 100644 --- a/src/nvim/normal.c +++ b/src/nvim/normal.c @@ -1197,6 +1197,15 @@ static void normal_check_interrupt(NormalState *s) } } +static void normal_check_window_scrolled(NormalState *s) +{ + // Trigger Scroll if the viewport changed. + if (!finish_op && has_event(EVENT_WINSCROLLED) + && win_did_scroll(curwin)) { + do_autocmd_winscrolled(curwin); + } +} + static void normal_check_cursor_moved(NormalState *s) { // Trigger CursorMoved if the cursor moved. @@ -1320,8 +1329,13 @@ static int normal_check(VimState *state) if (skip_redraw || exmode_active) { skip_redraw = false; } else if (do_redraw || stuff_empty()) { + // Need to make sure w_topline and w_leftcol are correct before + // normal_check_window_scrolled() is called. + update_topline(); + normal_check_cursor_moved(s); normal_check_text_changed(s); + normal_check_window_scrolled(s); // Updating diffs from changed() does not always work properly, // esp. updating folds. Do an update just before redrawing if @@ -4111,10 +4125,10 @@ void scroll_redraw(int up, long count) int prev_topfill = curwin->w_topfill; linenr_T prev_lnum = curwin->w_cursor.lnum; - if (up) - scrollup(count, true); - else + bool moved = up ? + scrollup(count, true) : scrolldown(count, true); + if (get_scrolloff_value()) { // Adjust the cursor position for 'scrolloff'. Mark w_topline as // valid, otherwise the screen jumps back at the end of the file. @@ -4146,7 +4160,8 @@ void scroll_redraw(int up, long count) } if (curwin->w_cursor.lnum != prev_lnum) coladvance(curwin->w_curswant); - curwin->w_viewport_invalid = true; + if (moved) + curwin->w_viewport_invalid = true; redraw_later(curwin, VALID); } diff --git a/src/nvim/window.c b/src/nvim/window.c index 2147bd4fc7..47b6b7e713 100644 --- a/src/nvim/window.c +++ b/src/nvim/window.c @@ -4976,6 +4976,30 @@ void shell_new_columns(void) } /* + * Check if "wp" has scrolled since last time it was checked + */ +bool win_did_scroll(win_T *wp) +{ + return (curwin->w_last_topline != curwin->w_topline || + curwin->w_last_leftcol != curwin->w_leftcol || + curwin->w_last_width != curwin->w_width || + curwin->w_last_height != curwin->w_height); +} + +/* + * Trigger WinScrolled autocmd + */ +void do_autocmd_winscrolled(win_T *wp) +{ + apply_autocmds(EVENT_WINSCROLLED, NULL, NULL, false, curbuf); + + wp->w_last_topline = wp->w_topline; + wp->w_last_leftcol = wp->w_leftcol; + wp->w_last_width = wp->w_width; + wp->w_last_height = wp->w_height; +} + +/* * Save the size of all windows in "gap". */ void win_size_save(garray_T *gap) diff --git a/test/functional/autocmd/winscrolled_spec.lua b/test/functional/autocmd/winscrolled_spec.lua new file mode 100644 index 0000000000..646baf08ea --- /dev/null +++ b/test/functional/autocmd/winscrolled_spec.lua @@ -0,0 +1,63 @@ +local helpers = require('test.functional.helpers')(after_each) + +local clear = helpers.clear +local eq = helpers.eq +local eval = helpers.eval +local funcs = helpers.funcs +local source = helpers.source + +describe('WinScrolled', function() + before_each(clear) + + it('is triggered by scrolling vertically', function() + source([[ + set nowrap + let width = winwidth(0) + let line = '123' . repeat('*', width * 2) + let lines = [line, line] + call nvim_buf_set_lines(0, 0, -1, v:true, lines) + + let g:scrolled = 0 + autocmd WinScrolled * let g:scrolled += 1 + execute "normal! \<C-e>" + ]]) + eq(1, eval('g:scrolled')) + end) + + it('is triggered by scrolling horizontally', function() + source([[ + set nowrap + let width = winwidth(0) + let line = '123' . repeat('*', width * 2) + let lines = [line, line] + call nvim_buf_set_lines(0, 0, -1, v:true, lines) + + let g:scrolled = 0 + autocmd WinScrolled * let g:scrolled += 1 + execute "normal! zl" + ]]) + eq(1, eval('g:scrolled')) + end) + + it('is triggered when the window scrolls in insert mode', function() + source([[ + let height = winheight(0) + let lines = map(range(height * 2), {_, i -> string(i)}) + call nvim_buf_set_lines(0, 0, -1, v:true, lines) + + let g:scrolled = 0 + autocmd WinScrolled * let g:scrolled += 1 + call feedkeys("LA\<CR><Esc>", "n") + ]]) + eq(2, eval('g:scrolled')) + end) + + it('is triggered when the window is resized', function() + source([[ + let g:scrolled = 0 + autocmd WinScrolled * let g:scrolled += 1 + wincmd v + ]]) + eq(1, eval('g:scrolled')) + end) +end) |