Mercurial > vim
comparison src/testdir/samples/matchparen.vim @ 33844:58c9f11eae5b v9.0.2134
patch 9.0.2134: ml_get error when scrolling
Commit: https://github.com/vim/vim/commit/c4ffeddfe5bd1824650e9b911ed9245bf56c69e3
Author: Christian Brabandt <cb@256bit.org>
Date: Mon Nov 27 23:25:03 2023 +0100
patch 9.0.2134: ml_get error when scrolling
Problem: ml_get error when scrolling after delete
Solution: mark topline to be validated in main_loop
if it is larger than current buffers line
count
reset_lnums() is called after e.g. TextChanged autocommands and it may
accidentally cause curwin->w_topline to become invalid, e.g. if the
autocommand has deleted some lines.
So verify that curwin->w_topline points to a valid line and if not, mark
the window to have w_topline recalculated in main_loop() in
update_topline() after reset_lnums() returns.
fixes: #13568
fixes: #13578
Signed-off-by: Christian Brabandt <cb@256bit.org>
author | Christian Brabandt <cb@256bit.org> |
---|---|
date | Mon, 27 Nov 2023 23:30:04 +0100 |
parents | |
children |
comparison
equal
deleted
inserted
replaced
33843:dc37b35ded98 | 33844:58c9f11eae5b |
---|---|
1 " Vim plugin for showing matching parens | |
2 " Maintainer: The Vim Project <https://github.com/vim/vim> | |
3 " Last Change: 2023 Oct 20 | |
4 " Former Maintainer: Bram Moolenaar <Bram@vim.org> | |
5 | |
6 " Exit quickly when: | |
7 " - this plugin was already loaded (or disabled) | |
8 " - when 'compatible' is set | |
9 if exists("g:loaded_matchparen") || &cp | |
10 finish | |
11 endif | |
12 let g:loaded_matchparen = 1 | |
13 | |
14 if !exists("g:matchparen_timeout") | |
15 let g:matchparen_timeout = 300 | |
16 endif | |
17 if !exists("g:matchparen_insert_timeout") | |
18 let g:matchparen_insert_timeout = 60 | |
19 endif | |
20 | |
21 let s:has_matchaddpos = exists('*matchaddpos') | |
22 | |
23 augroup matchparen | |
24 " Replace all matchparen autocommands | |
25 autocmd! CursorMoved,CursorMovedI,WinEnter,BufWinEnter,WinScrolled * call s:Highlight_Matching_Pair() | |
26 autocmd! WinLeave,BufLeave * call s:Remove_Matches() | |
27 if exists('##TextChanged') | |
28 autocmd! TextChanged,TextChangedI * call s:Highlight_Matching_Pair() | |
29 autocmd! TextChangedP * call s:Remove_Matches() | |
30 endif | |
31 augroup END | |
32 | |
33 " Skip the rest if it was already done. | |
34 if exists("*s:Highlight_Matching_Pair") | |
35 finish | |
36 endif | |
37 | |
38 let s:cpo_save = &cpo | |
39 set cpo-=C | |
40 | |
41 " The function that is invoked (very often) to define a ":match" highlighting | |
42 " for any matching paren. | |
43 func s:Highlight_Matching_Pair() | |
44 if !exists("w:matchparen_ids") | |
45 let w:matchparen_ids = [] | |
46 endif | |
47 " Remove any previous match. | |
48 call s:Remove_Matches() | |
49 | |
50 " Avoid that we remove the popup menu. | |
51 " Return when there are no colors (looks like the cursor jumps). | |
52 if pumvisible() || (&t_Co < 8 && !has("gui_running")) | |
53 return | |
54 endif | |
55 | |
56 " Get the character under the cursor and check if it's in 'matchpairs'. | |
57 let c_lnum = line('.') | |
58 let c_col = col('.') | |
59 let before = 0 | |
60 | |
61 let text = getline(c_lnum) | |
62 let matches = matchlist(text, '\(.\)\=\%'.c_col.'c\(.\=\)') | |
63 if empty(matches) | |
64 let [c_before, c] = ['', ''] | |
65 else | |
66 let [c_before, c] = matches[1:2] | |
67 endif | |
68 let plist = split(&matchpairs, '.\zs[:,]') | |
69 let i = index(plist, c) | |
70 if i < 0 | |
71 " not found, in Insert mode try character before the cursor | |
72 if c_col > 1 && (mode() == 'i' || mode() == 'R') | |
73 let before = strlen(c_before) | |
74 let c = c_before | |
75 let i = index(plist, c) | |
76 endif | |
77 if i < 0 | |
78 " not found, nothing to do | |
79 return | |
80 endif | |
81 endif | |
82 | |
83 " Figure out the arguments for searchpairpos(). | |
84 if i % 2 == 0 | |
85 let s_flags = 'nW' | |
86 let c2 = plist[i + 1] | |
87 else | |
88 let s_flags = 'nbW' | |
89 let c2 = c | |
90 let c = plist[i - 1] | |
91 endif | |
92 if c == '[' | |
93 let c = '\[' | |
94 let c2 = '\]' | |
95 endif | |
96 | |
97 " Find the match. When it was just before the cursor move it there for a | |
98 " moment. | |
99 if before > 0 | |
100 let has_getcurpos = exists("*getcurpos") | |
101 if has_getcurpos | |
102 " getcurpos() is more efficient but doesn't exist before 7.4.313. | |
103 let save_cursor = getcurpos() | |
104 else | |
105 let save_cursor = winsaveview() | |
106 endif | |
107 call cursor(c_lnum, c_col - before) | |
108 endif | |
109 | |
110 if !has("syntax") || !exists("g:syntax_on") | |
111 let s_skip = "0" | |
112 else | |
113 " Build an expression that detects whether the current cursor position is | |
114 " in certain syntax types (string, comment, etc.), for use as | |
115 " searchpairpos()'s skip argument. | |
116 " We match "escape" for special items, such as lispEscapeSpecial, and | |
117 " match "symbol" for lispBarSymbol. | |
118 let s_skip = 'synstack(".", col("."))' | |
119 \ . '->indexof({_, id -> synIDattr(id, "name") =~? ' | |
120 \ . '"string\\|character\\|singlequote\\|escape\\|symbol\\|comment"}) >= 0' | |
121 " If executing the expression determines that the cursor is currently in | |
122 " one of the syntax types, then we want searchpairpos() to find the pair | |
123 " within those syntax types (i.e., not skip). Otherwise, the cursor is | |
124 " outside of the syntax types and s_skip should keep its value so we skip | |
125 " any matching pair inside the syntax types. | |
126 " Catch if this throws E363: pattern uses more memory than 'maxmempattern'. | |
127 try | |
128 execute 'if ' . s_skip . ' | let s_skip = "0" | endif' | |
129 catch /^Vim\%((\a\+)\)\=:E363/ | |
130 " We won't find anything, so skip searching, should keep Vim responsive. | |
131 return | |
132 endtry | |
133 endif | |
134 | |
135 " Limit the search to lines visible in the window. | |
136 let stoplinebottom = line('w$') | |
137 let stoplinetop = line('w0') | |
138 if i % 2 == 0 | |
139 let stopline = stoplinebottom | |
140 else | |
141 let stopline = stoplinetop | |
142 endif | |
143 | |
144 " Limit the search time to 300 msec to avoid a hang on very long lines. | |
145 " This fails when a timeout is not supported. | |
146 if mode() == 'i' || mode() == 'R' | |
147 let timeout = exists("b:matchparen_insert_timeout") ? b:matchparen_insert_timeout : g:matchparen_insert_timeout | |
148 else | |
149 let timeout = exists("b:matchparen_timeout") ? b:matchparen_timeout : g:matchparen_timeout | |
150 endif | |
151 try | |
152 let [m_lnum, m_col] = searchpairpos(c, '', c2, s_flags, s_skip, stopline, timeout) | |
153 catch /E118/ | |
154 " Can't use the timeout, restrict the stopline a bit more to avoid taking | |
155 " a long time on closed folds and long lines. | |
156 " The "viewable" variables give a range in which we can scroll while | |
157 " keeping the cursor at the same position. | |
158 " adjustedScrolloff accounts for very large numbers of scrolloff. | |
159 let adjustedScrolloff = min([&scrolloff, (line('w$') - line('w0')) / 2]) | |
160 let bottom_viewable = min([line('$'), c_lnum + &lines - adjustedScrolloff - 2]) | |
161 let top_viewable = max([1, c_lnum-&lines+adjustedScrolloff + 2]) | |
162 " one of these stoplines will be adjusted below, but the current values are | |
163 " minimal boundaries within the current window | |
164 if i % 2 == 0 | |
165 if has("byte_offset") && has("syntax_items") && &smc > 0 | |
166 let stopbyte = min([line2byte("$"), line2byte(".") + col(".") + &smc * 2]) | |
167 let stopline = min([bottom_viewable, byte2line(stopbyte)]) | |
168 else | |
169 let stopline = min([bottom_viewable, c_lnum + 100]) | |
170 endif | |
171 let stoplinebottom = stopline | |
172 else | |
173 if has("byte_offset") && has("syntax_items") && &smc > 0 | |
174 let stopbyte = max([1, line2byte(".") + col(".") - &smc * 2]) | |
175 let stopline = max([top_viewable, byte2line(stopbyte)]) | |
176 else | |
177 let stopline = max([top_viewable, c_lnum - 100]) | |
178 endif | |
179 let stoplinetop = stopline | |
180 endif | |
181 let [m_lnum, m_col] = searchpairpos(c, '', c2, s_flags, s_skip, stopline) | |
182 endtry | |
183 | |
184 if before > 0 | |
185 if has_getcurpos | |
186 call setpos('.', save_cursor) | |
187 else | |
188 call winrestview(save_cursor) | |
189 endif | |
190 endif | |
191 | |
192 " If a match is found setup match highlighting. | |
193 if m_lnum > 0 && m_lnum >= stoplinetop && m_lnum <= stoplinebottom | |
194 if s:has_matchaddpos | |
195 call add(w:matchparen_ids, matchaddpos('MatchParen', [[c_lnum, c_col - before], [m_lnum, m_col]], 10)) | |
196 else | |
197 exe '3match MatchParen /\(\%' . c_lnum . 'l\%' . (c_col - before) . | |
198 \ 'c\)\|\(\%' . m_lnum . 'l\%' . m_col . 'c\)/' | |
199 call add(w:matchparen_ids, 3) | |
200 endif | |
201 let w:paren_hl_on = 1 | |
202 endif | |
203 endfunction | |
204 | |
205 func s:Remove_Matches() | |
206 if exists('w:paren_hl_on') && w:paren_hl_on | |
207 while !empty(w:matchparen_ids) | |
208 silent! call remove(w:matchparen_ids, 0)->matchdelete() | |
209 endwhile | |
210 let w:paren_hl_on = 0 | |
211 endif | |
212 endfunc | |
213 | |
214 " Define commands that will disable and enable the plugin. | |
215 command DoMatchParen call s:DoMatchParen() | |
216 command NoMatchParen call s:NoMatchParen() | |
217 | |
218 func s:NoMatchParen() | |
219 let w = winnr() | |
220 noau windo silent! call matchdelete(3) | |
221 unlet! g:loaded_matchparen | |
222 exe "noau ". w . "wincmd w" | |
223 au! matchparen | |
224 endfunc | |
225 | |
226 func s:DoMatchParen() | |
227 runtime plugin/matchparen.vim | |
228 let w = winnr() | |
229 silent windo doau CursorMoved | |
230 exe "noau ". w . "wincmd w" | |
231 endfunc | |
232 | |
233 let &cpo = s:cpo_save | |
234 unlet s:cpo_save |