diff options
author | LemonBoy <thatlemon@gmail.com> | 2022-04-08 15:18:45 +0100 |
---|---|---|
committer | Bram Moolenaar <Bram@vim.org> | 2022-04-08 15:18:45 +0100 |
commit | 0937182d49fa8db50cec42785f22f1031760a0bd (patch) | |
tree | a41ab36fcbeb5b2f0bc91ce36b2d056af2ec2491 | |
parent | 18ee0f603ebd3c091f6d2ab88e652fda32821048 (diff) | |
download | vim-git-0937182d49fa8db50cec42785f22f1031760a0bd.tar.gz |
patch 8.2.4713: plugins cannot track text scrollingv8.2.4713
Problem: Plugins cannot track text scrolling.
Solution: Add the WinScrolled event. (closes #10102)
-rw-r--r-- | runtime/doc/autocmd.txt | 25 | ||||
-rw-r--r-- | src/autocmd.c | 22 | ||||
-rw-r--r-- | src/edit.c | 3 | ||||
-rw-r--r-- | src/gui.c | 3 | ||||
-rw-r--r-- | src/main.c | 8 | ||||
-rw-r--r-- | src/proto/autocmd.pro | 1 | ||||
-rw-r--r-- | src/proto/window.pro | 3 | ||||
-rw-r--r-- | src/structs.h | 6 | ||||
-rw-r--r-- | src/testdir/test_autocmd.vim | 55 | ||||
-rw-r--r-- | src/version.c | 2 | ||||
-rw-r--r-- | src/vim.h | 1 | ||||
-rw-r--r-- | src/window.c | 29 |
12 files changed, 153 insertions, 5 deletions
diff --git a/runtime/doc/autocmd.txt b/runtime/doc/autocmd.txt index 16bbbf580..beb1f2e3a 100644 --- a/runtime/doc/autocmd.txt +++ b/runtime/doc/autocmd.txt @@ -402,6 +402,8 @@ Name triggered by ~ |User| to be used in combination with ":doautocmd" |SigUSR1| after the SIGUSR1 signal has been detected +|WinScrolled| after scrolling or resizing a window + The alphabetical list of autocommand events: *autocmd-events-abc* @@ -1228,7 +1230,13 @@ User Never executed automatically. To be used for Note that when `:doautocmd User MyEvent` is used while there are no matching autocommands, you will get an error. If you don't want - that, define a dummy autocommand yourself. + that, either check whether an autocommand is + defined using `exists('#User#MyEvent')` or + define a dummy autocommand yourself. + Example: > + if exists('#User#MyEvent') + doautocmd User MyEvent + endif *SigUSR1* SigUSR1 After the SIGUSR1 signal has been detected. @@ -1317,10 +1325,23 @@ WinNew When a new window was created. Not done for the first window, when Vim has just started. Before a WinEnter event. + *WinScrolled* +WinScrolled After scrolling the content of a window or + resizing a window. + The pattern is matched against the + |window-ID|. Both <amatch> and <afile> are + set to the |window-ID|. + Non-recursive (the event cannot trigger + itself). However, if the command causes the + window to scroll or change size another + WinScrolled event will be triggered later. + Does not trigger when the command is added, + only after the first scroll or resize. + ============================================================================== 6. Patterns *autocmd-patterns* *{aupat}* -The {aupat} argument of `:autocmd` can be a comma separated list. This works as +The {aupat} argument of `:autocmd` can be a comma-separated list. This works as if the command was given with each pattern separately. Thus this command: > :autocmd BufRead *.txt,*.info set et Is equivalent to: > diff --git a/src/autocmd.c b/src/autocmd.c index 7eb08c85b..a0065decb 100644 --- a/src/autocmd.c +++ b/src/autocmd.c @@ -190,6 +190,7 @@ static struct event_name {"WinClosed", EVENT_WINCLOSED}, {"WinEnter", EVENT_WINENTER}, {"WinLeave", EVENT_WINLEAVE}, + {"WinScrolled", EVENT_WINSCROLLED}, {"VimResized", EVENT_VIMRESIZED}, {"TextYankPost", EVENT_TEXTYANKPOST}, {"VimSuspend", EVENT_VIMSUSPEND}, @@ -1251,6 +1252,15 @@ do_autocmd_event( vim_free(rettv.vval.v_string); } #endif + // Initialize the fields checked by the WinScrolled trigger to + // stop it from firing right after the first autocmd is defined. + if (event == EVENT_WINSCROLLED && !has_winscrolled()) + { + 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; + } if (is_buflocal) { @@ -1783,6 +1793,15 @@ trigger_cursorhold(void) } /* + * Return TRUE when there is a WinScrolled autocommand defined. + */ + int +has_winscrolled(void) +{ + return (first_autopat[(int)EVENT_WINSCROLLED] != NULL); +} + +/* * Return TRUE when there is a CursorMoved autocommand defined. */ int @@ -2078,7 +2097,8 @@ apply_autocmds_group( || event == EVENT_DIRCHANGEDPRE || event == EVENT_MODECHANGED || event == EVENT_USER - || event == EVENT_WINCLOSED) + || event == EVENT_WINCLOSED + || event == EVENT_WINSCROLLED) { fname = vim_strsave(fname); autocmd_fname_full = TRUE; // don't expand it later diff --git a/src/edit.c b/src/edit.c index f30edd5c7..1585f8518 100644 --- a/src/edit.c +++ b/src/edit.c @@ -1527,6 +1527,9 @@ ins_redraw(int ready) // not busy with something (linenr_T)(curwin->w_cursor.lnum + 1)); } + if (ready) + may_trigger_winscrolled(curwin); + // Trigger SafeState if nothing is pending. may_trigger_safestate(ready && !ins_compl_active() @@ -5237,6 +5237,9 @@ gui_update_screen(void) last_cursormoved = curwin->w_cursor; } + if (!finish_op) + may_trigger_winscrolled(curwin); + # ifdef FEAT_CONCEAL if (conceal_update_lines && (conceal_old_cursor_line != conceal_new_cursor_line diff --git a/src/main.c b/src/main.c index 2a3d310ab..fe3571b92 100644 --- a/src/main.c +++ b/src/main.c @@ -1336,6 +1336,14 @@ main_loop( curbuf->b_last_changedtick = CHANGEDTICK(curbuf); } + // Ensure curwin->w_topline and curwin->w_leftcol are up to date + // before triggering a WinScrolled autocommand. + update_topline(); + validate_cursor(); + + if (!finish_op) + may_trigger_winscrolled(curwin); + // If nothing is pending and we are going to wait for the user to // type a character, trigger SafeState. may_trigger_safestate(!op_pending() && restart_edit == 0); diff --git a/src/proto/autocmd.pro b/src/proto/autocmd.pro index d8145dfe7..366d7aa4d 100644 --- a/src/proto/autocmd.pro +++ b/src/proto/autocmd.pro @@ -26,6 +26,7 @@ int has_cmdundefined(void); int has_textyankpost(void); int has_completechanged(void); int has_modechanged(void); +int has_winscrolled(void); void block_autocmds(void); void unblock_autocmds(void); int is_autocmd_blocked(void); diff --git a/src/proto/window.pro b/src/proto/window.pro index 1954dfd0b..589dd0931 100644 --- a/src/proto/window.pro +++ b/src/proto/window.pro @@ -13,14 +13,15 @@ int make_windows(int count, int vertical); void win_move_after(win_T *win1, win_T *win2); void win_equal(win_T *next_curwin, int current, int dir); void entering_window(win_T *win); +void curwin_init(void); void close_windows(buf_T *buf, int keep_curwin); int one_window(void); int win_close(win_T *win, int free_buf); +void may_trigger_winscrolled(win_T *wp); void win_close_othertab(win_T *win, int free_buf, tabpage_T *tp); void win_free_all(void); win_T *winframe_remove(win_T *win, int *dirp, tabpage_T *tp); void close_others(int message, int forceit); -void curwin_init(void); int win_alloc_first(void); win_T *win_alloc_popup_win(void); void win_init_popup_win(win_T *wp, buf_T *buf); diff --git a/src/structs.h b/src/structs.h index b8648a545..738c59231 100644 --- a/src/structs.h +++ b/src/structs.h @@ -3510,6 +3510,12 @@ struct window_S // window #endif + // four fields that are only used when there is a WinScrolled autocommand + linenr_T w_last_topline; // last known value for w_topline + colnr_T w_last_leftcol; // last known value for w_leftcol + int w_last_width; // last known value for w_width + int w_last_height; // last known value for w_height + /* * Layout of the window in the screen. * May need to add "msg_scrolled" to "w_winrow" in rare situations. diff --git a/src/testdir/test_autocmd.vim b/src/testdir/test_autocmd.vim index eb1fa046c..3ff9d0b34 100644 --- a/src/testdir/test_autocmd.vim +++ b/src/testdir/test_autocmd.vim @@ -3,6 +3,7 @@ source shared.vim source check.vim source term_util.vim +source screendump.vim import './vim9.vim' as v9 func s:cleanup_buffers() abort @@ -309,6 +310,60 @@ func Test_win_tab_autocmd() unlet g:record endfunc +func Test_WinScrolled() + CheckRunVimInTerminal + + let lines =<< trim END + set nowrap scrolloff=0 + for ii in range(1, 18) + call setline(ii, repeat(nr2char(96 + ii), ii * 2)) + endfor + let win_id = win_getid() + let g:matched = v:false + execute 'au WinScrolled' win_id 'let g:matched = v:true' + let g:scrolled = 0 + au WinScrolled * let g:scrolled += 1 + au WinScrolled * let g:amatch = str2nr(expand('<amatch>')) + au WinScrolled * let g:afile = str2nr(expand('<afile>')) + END + call writefile(lines, 'Xtest_winscrolled') + let buf = RunVimInTerminal('-S Xtest_winscrolled', {'rows': 6}) + + call term_sendkeys(buf, ":echo g:scrolled\<CR>") + call WaitForAssert({-> assert_match('^0 ', term_getline(buf, 6))}, 1000) + + " Scroll left/right in Normal mode. + call term_sendkeys(buf, "zlzh:echo g:scrolled\<CR>") + call WaitForAssert({-> assert_match('^2 ', term_getline(buf, 6))}, 1000) + + " Scroll up/down in Normal mode. + call term_sendkeys(buf, "\<c-e>\<c-y>:echo g:scrolled\<CR>") + call WaitForAssert({-> assert_match('^4 ', term_getline(buf, 6))}, 1000) + + " Scroll up/down in Insert mode. + call term_sendkeys(buf, "Mi\<c-x>\<c-e>\<Esc>i\<c-x>\<c-y>\<Esc>") + call term_sendkeys(buf, ":echo g:scrolled\<CR>") + call WaitForAssert({-> assert_match('^6 ', term_getline(buf, 6))}, 1000) + + " Scroll the window horizontally to focus the last letter of the third line + " containing only six characters. Moving to the previous and shorter lines + " should trigger another autocommand as Vim has to make them visible. + call term_sendkeys(buf, "5zl2k") + call term_sendkeys(buf, ":echo g:scrolled\<CR>") + call WaitForAssert({-> assert_match('^8 ', term_getline(buf, 6))}, 1000) + + " Ensure the command was triggered for the specified window ID. + call term_sendkeys(buf, ":echo g:matched\<CR>") + call WaitForAssert({-> assert_match('^v:true ', term_getline(buf, 6))}, 1000) + + " Ensure the expansion of <amatch> and <afile> matches the window ID. + call term_sendkeys(buf, ":echo g:amatch == win_id && g:afile == win_id\<CR>") + call WaitForAssert({-> assert_match('^v:true ', term_getline(buf, 6))}, 1000) + + call StopVimInTerminal(buf) + call delete('Xtest_winscrolled') +endfunc + func Test_WinClosed() " Test that the pattern is matched against the closed window's ID, and both " <amatch> and <afile> are set to it. diff --git a/src/version.c b/src/version.c index cebb0d018..15d02e141 100644 --- a/src/version.c +++ b/src/version.c @@ -747,6 +747,8 @@ static char *(features[]) = static int included_patches[] = { /* Add new patch number below this line */ /**/ + 4713, +/**/ 4712, /**/ 4711, @@ -1386,6 +1386,7 @@ enum auto_event EVENT_WINCLOSED, // after closing a window EVENT_VIMSUSPEND, // before Vim is suspended EVENT_VIMRESUME, // after Vim is resumed + EVENT_WINSCROLLED, // after Vim window was scrolled NUM_EVENTS // MUST be the last one }; diff --git a/src/window.c b/src/window.c index f76347589..1eab3dcce 100644 --- a/src/window.c +++ b/src/window.c @@ -2779,11 +2779,38 @@ trigger_winclosed(win_T *win) if (recursive) return; recursive = TRUE; - vim_snprintf((char *)winid, sizeof(winid), "%i", win->w_id); + vim_snprintf((char *)winid, sizeof(winid), "%d", win->w_id); apply_autocmds(EVENT_WINCLOSED, winid, winid, FALSE, win->w_buffer); recursive = FALSE; } + void +may_trigger_winscrolled(win_T *wp) +{ + static int recursive = FALSE; + char_u winid[NUMBUFLEN]; + + if (recursive || !has_winscrolled()) + return; + + if (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) + { + vim_snprintf((char *)winid, sizeof(winid), "%d", wp->w_id); + + recursive = TRUE; + apply_autocmds(EVENT_WINSCROLLED, winid, winid, FALSE, wp->w_buffer); + recursive = FALSE; + + 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; + } +} + /* * Close window "win" in tab page "tp", which is not the current tab page. * This may be the last window in that tab page and result in closing the tab, |