This PR refactors the default matchparen plugin in Vim9 script. It also includes fixes for these issues:
A few comments regarding the original version of the plugin.
vim/runtime/plugin/matchparen.vim
Line 9 in 5f91e74
| if exists("g:loaded_matchparen") || &cp || !exists("##CursorMoved") |
The last exists() check is useless. CursorMoved already existed in 7.0.001 which is 15 years old.
vim/runtime/plugin/matchparen.vim
Line 23 in 5f91e74
| autocmd! CursorMoved,CursorMovedI,WinEnter * call s:Highlight_Matching_Pair() |
CursorMoved is fired very often. It would help to delay the function with a timer, and a short – configurable – waiting time. I suggest g:matchparen_config.highlight_delay.
The idea of using a timer comes from the vim-parenmatch plugin, whose author is credited in the new version.
vim/runtime/plugin/matchparen.vim
Line 25 in 5f91e74
| if exists('##TextChanged') |
This check is useless; TextChanged was introduced in 7.3.883 which is almost 8 years old.
vim/runtime/plugin/matchparen.vim
Lines 30 to 33 in 5f91e74
| " Skip the rest if it was already done. | |
| if exists("*s:Highlight_Matching_Pair") | |
| finish | |
| endif |
The only reason why this guard is currently necessary is to let :DoMatchParen re-enable the plugin (by re-installing its autocmds):
vim/runtime/plugin/matchparen.vim
Line 217 in 5f91e74
| runtime plugin/matchparen.vim |
without the functions being redefined; otherwise an error would be raised because :func is never followed by a bang.
A better design would be to wrap the autocmds inside a function which could install or clear them on-demand. This way, no need for a guard and no need to partially reload the plugin (which often creates issues).
vim/runtime/plugin/matchparen.vim
Line 46 in 5f91e74
| if pumvisible() || (&t_Co < 8 && !has("gui_running")) |
Since s:Highlight_Matching_Pair() is invoked on every motion, the last two checks are inefficient. If – on a given motion – the terminal doesn't support colors, or we're not in the GUI, Vim won't start to support more colors or be in the GUI on the next motion. Actually, it could (via :gui), but this is a corner-case which doesn't warrant checking on every motion. It would be more efficient to move these checks at the start of the plugin.
vim/runtime/plugin/matchparen.vim
Line 56 in 5f91e74
| let matches = matchlist(text, '\(.\)\=\%'.c_col.'c\(.\=\)') |
Using a regex to get the character under the cursor (and the one before) is inefficient. The fastest method (that I know of) is:
getline('.')[charcol('.') - 1]
This is only reliable in Vim9 script though; if there is a multibyte character under the cursor, it would not work as expected in a legacy function. In legacy Vim script, the fastest method (that I know of) is:
getline('.')->strpart(col('.') - 1, 1, v:true)
For the character before the cursor:
# Vim9
getline('.')->strpart(0, col('.') - 1)[-1]
" legacy
getline('.')->slice(charcol('.') - 2, charcol('.') - 1)
vim/runtime/plugin/matchparen.vim
Line 62 in 5f91e74
| let plist = split(&matchpairs, '.\zs[:,]') |
Since s:Highlight_Matching_Pair() is invoked on every motion, parsing 'matchpairs' here is inefficient. It would be faster to parse the option on these events:
┌──────────────────────┬─────────────────────────────────────────────────────────────────┐
│ WinEnter │ for when we focus a different window │
├──────────────────────┼─────────────────────────────────────────────────────────────────┤
│ BufWinEnter │ for when we stay in the same window but load a different buffer │
├──────────────────────┼─────────────────────────────────────────────────────────────────┤
│ VimEnter │ for when we start Vim │
├──────────────────────┼─────────────────────────────────────────────────────────────────┤
│ FileType │ for when a filetype plugin resets 'matchpairs' │
├──────────────────────┼─────────────────────────────────────────────────────────────────┤
│ OptionSet matchpairs │ for when we manually reset 'matchpairs' │
└──────────────────────┴─────────────────────────────────────────────────────────────────┘
That's what the vim-parenmatch plugin does, from which – again – I took the idea.
vim/runtime/plugin/matchparen.vim
Line 66 in 5f91e74
| if c_col > 1 && (mode() == 'i' || mode() == 'R') |
vim/runtime/plugin/matchparen.vim
Line 138 in 5f91e74
| if mode() == 'i' || mode() == 'R' |
Evaluating mode() on these 2 lines is inefficient. It won't change in-between. It's better to save it in a variable.
vim/runtime/plugin/matchparen.vim
Line 94 in 5f91e74
| let has_getcurpos = exists("*getcurpos") |
This check is useless. Vim supports getcurpos() since 7.4.313 which is 7 years old.
vim/runtime/plugin/matchparen.vim
Line 104 in 5f91e74
| if !has("syntax") || !exists("g:syntax_on") |
The has("syntax") check is inefficient. Vim can't start supporting syntax highlighting in-between 2 motions. It would be better to get the skip expression from another function whose definition would be different depending on whether Vim was compiled with syntax highlighting. One might argue that the gain is negated by the new extra function call; that might be true, but I think it lets us write a more readable code by moving all the logic responsible for determining which skip expression to use in a separate function.
Also, inspecting the syntax under the cursor might be too costly for some users (it depends on their machine capabilities and the type of files they edit); they should have a chance to disable the check. That's why I suggest a new configuration option: g:matchparen_config.syntax_ignored.
vim/runtime/plugin/matchparen.vim
Lines 111 to 112 in 5f91e74
| let s_skip = '!empty(filter(map(synstack(line("."), col(".")), ''synIDattr(v:val, "name")''), ' . | |
| \ '''v:val =~? "string\\|character\\|singlequote\\|escape\\|comment"''))' |
It would be more readable if it was rewritten using lambdas and method calls:
let S_skip = {-> !synstack('.', col('.'))->map({_, v -> synIDattr(v, 'name')})->filter({_, v -> v =~? 'string\|character\|singlequote\|escape\|comment'})->empty()}
Also, no need of filter() + empty(); match() can do the same thing, is easier to read, and might sometimes be a little faster:
let S_skip = {-> synstack('.', col('.'))->map({_, v -> synIDattr(v, 'name')})->match('\cstring\|character\|singlequote\|escape\|comment') >= 0}
And no need of line('.'); synstack() translates '.' as the current line number automatically, when it's its first argument. BTW, it would be nice if it could do the same for col('.'):
# weird because asymmetrical
synstack('.', col('.'))
# nice because symmetrical
synstack('.', '.')
vim/runtime/plugin/matchparen.vim
Lines 113 to 120 in 5f91e74
| " If executing the expression determines that the cursor is currently in | |
| " one of the syntax types, then we want searchpairpos() to find the pair | |
| " within those syntax types (i.e., not skip). Otherwise, the cursor is | |
| " outside of the syntax types and s_skip should keep its value so we skip | |
| " any matching pair inside the syntax types. | |
| " Catch if this throws E363: pattern uses more memory than 'maxmempattern'. | |
| try | |
| execute 'if ' . s_skip . ' | let s_skip = "0" | endif' |
The comment and the code both contain an error.
we want searchpairpos() to find the pair within those syntax types (i.e., not skip)
Making searchpairpos() looking for a pair within specific syntax types (strings/comments/...) is not equivalent to disabling the skip expression; it's equivalent to inverting the skip expression. So, the code should not be:
execute 'if ' . s_skip . ' | let s_skip = "0" | endif'
But rather:
execute 'if ' . s_skip . ' | let s_skip = "!" . s_skip | endif'
Actually, it still needs to be adapted to the previously mentioned lambdas; the previous line just gives an idea of what should be done. Anyway, the new version fixes this issue.
vim/runtime/plugin/matchparen.vim
Line 144 in 5f91e74
| let [m_lnum, m_col] = searchpairpos(c, '', c2, s_flags, s_skip, stopline, timeout) |
Because of synstack() inside the skip expression, searchpair() could raise E363. It should be caught to fix these kind of issues:
vim/runtime/plugin/matchparen.vim
Lines 145 to 173 in 5f91e74
| catch /E118/ | |
| " Can't use the timeout, restrict the stopline a bit more to avoid taking | |
| " a long time on closed folds and long lines. | |
| " The "viewable" variables give a range in which we can scroll while | |
| " keeping the cursor at the same position. | |
| " adjustedScrolloff accounts for very large numbers of scrolloff. | |
| let adjustedScrolloff = min([&scrolloff, (line('w$') - line('w0')) / 2]) | |
| let bottom_viewable = min([line('$'), c_lnum + &lines - adjustedScrolloff - 2]) | |
| let top_viewable = max([1, c_lnum-&lines+adjustedScrolloff + 2]) | |
| " one of these stoplines will be adjusted below, but the current values are | |
| " minimal boundaries within the current window | |
| if i % 2 == 0 | |
| if has("byte_offset") && has("syntax_items") && &smc > 0 | |
| let stopbyte = min([line2byte("$"), line2byte(".") + col(".") + &smc * 2]) | |
| let stopline = min([bottom_viewable, byte2line(stopbyte)]) | |
| else | |
| let stopline = min([bottom_viewable, c_lnum + 100]) | |
| endif | |
| let stoplinebottom = stopline | |
| else | |
| if has("byte_offset") && has("syntax_items") && &smc > 0 | |
| let stopbyte = max([1, line2byte(".") + col(".") - &smc * 2]) | |
| let stopline = max([top_viewable, byte2line(stopbyte)]) | |
| else | |
| let stopline = max([top_viewable, c_lnum - 100]) | |
| endif | |
| let stoplinetop = stopline | |
| endif | |
| let [m_lnum, m_col] = searchpairpos(c, '', c2, s_flags, s_skip, stopline) |
All of this code is useless and makes the plugin harder to read/maintain. Its original purpose was to handle the case where searchpairpos() doesn't support {timeout}. It does since 7.1.211 which is more than 13 years old; there's no way E118 can be raised nowadays.
vim/runtime/plugin/matchparen.vim
Line 186 in 5f91e74
| if exists('*matchaddpos') |
This check is useless; Vim supports matchaddpos() since 7.4.330 which is 7 years old.
vim/runtime/plugin/matchparen.vim
Lines 187 to 190 in 5f91e74
| call matchaddpos('MatchParen', [[c_lnum, c_col - before], [m_lnum, m_col]], 10, 3) | |
| else | |
| exe '3match MatchParen /\(\%' . c_lnum . 'l\%' . (c_col - before) . | |
| \ 'c\)\|\(\%' . m_lnum . 'l\%' . m_col . 'c\)/' |
Using a match can create weird issues, such as:
Text properties would be better. We need prop_type_add(), prop_add() and prop_remove(); they were added in 8.1.0579 which is more than 2 years old.
vim/runtime/plugin/matchparen.vim
Lines 205 to 206 in 5f91e74
| command DoMatchParen call s:DoMatchParen() | |
| command NoMatchParen call s:NoMatchParen() |
The commands should be defined with the -bar attribute so that they can be used in a bar-separated sequence of commands, which is common in an autocmd:
autocmd SomeEvent * if 'some condition' | NoMatchParen | else | DoMatchParen | endif
Currently, this raises an unexpected error:
E488: Trailing characters: | else | DoMatchParen | endif
Also, it would have been better to use the same prefix for the command names:
# bad
:DoMatchParen
:NoMatchParen
# good
:MatchParenOn
:MatchParenOff
This would reduce irrelevant suggestions when using tab completion on the command-line. Obviously, we can't do that now if we want to be backwards compatible. However, we could introduce a new boolean option (g:matchparen_config.old_commands) which would let the user prevent their installation. In any case, the commands would always be installed under their new names.
And since we provide toggling commands, it means we acknowledge the fact that the user might not want the plugin to be enabled all the time. Thus, it makes sense to also provide an option which lets the user disable the plugin on startup:
g:matchparen_config = {
...
on_startup: false,
...
}
Which might be easier to read for some people than this:
au VimEnter * MatchParenOff
vim/runtime/plugin/matchparen.vim
Line 210 in 5f91e74
| noau windo silent! call matchdelete(3) |
Even with :noau, :windo has unavoidable side-effects. For example, if an inactive window is squashed to 0 lines (only possible if 'winminheigh' was set to 0), :windo will reset its height to 1 line. Instead, we should iterate over the window IDs, and – on each of them – run the command via win_execute(). The latter has fewer side-effects (if not none). Example:
getwininfo()->map((_, v) => win_execute(v.winid, 'silent! matchdelete(3)'))
However, all of this is actually useless. Since this commit, the matchparen plugin has a WinLeave autocmd which removes matches when leaving a window. The purpose was to fix the issue #1244. Now, there can only be 1 window where matches are active. So, we only need to remove them in the current window, for which :windo is overkill. Extra benefit: without :windo, there's no need to save and restore the active window either.
vim/runtime/plugin/matchparen.vim
Line 217 in 5f91e74
| runtime plugin/matchparen.vim |
Already mentioned, but re-sourcing the plugin is awkward. The autocmds should be wrapped inside a function, and this function should be called here.
vim/runtime/plugin/matchparen.vim
Line 219 in 5f91e74
| silent windo doau CursorMoved |
Again, I don't think :windo makes sense. The matches should only be highlighted in the current window, because they're tied to the cursor. And only 1 window has the cursor.
Also, this is not very readable ("Why firing CursorMoved here?"), and firing CursorMoved could have undesirable side-effects. In any case, that's not what we really want here. What we really want is to highlight the matches; that's the job of s:Highlight_Matching_Pair(), which should be called here.
Lines 42 to 43 in 5f91e74
| The search is limited to avoid a delay when moving the cursor. The limits | |
| are: |
Since we might introduce the config option g:matchparen_config.highlight_delay, which already uses the term "delay", it would be useful to use another term to avoid any confusion. How about "lag"? If there's an issue, I think that's how a lot of people would frame the issue in a bug report.
Lines 45 to 48 in 5f91e74
| - 100 lines above or below the cursor to avoid a long delay when there are | |
| closed folds. | |
| - 'synmaxcol' times 2 bytes before or after the cursor to avoid a delay | |
| in a long line with syntax highlighting. |
That was relevant when the complex block of code after the catch /E118/ statement could sometimes be processed; i.e. when matchaddpos() did not support the optional {timeout} argument. That's no longer the case. This block of code is now dead, and this excerpt is stale.
Test for the issue #1532:
vim -Nu NORC -S <(cat <<'EOF'
vim9script
set lines=24
var lines =<< trim END
1
2
3 wrapped on 15 lines wrapped on 15 lines wrapped on 15 lines wrapped on 15 lines wrapped on 15 lines wrapped on 15 lines wrapped on 15 lines wrapped on 15 lines wrapped on 15 lines wrapped on 15 lines wrapped on 15 lines wrapped on 15 lines wrapped on 15 lines wrapped on 15 lines wrapped on 15 lines wrapped on 15 lines wrapped on 15 lines wrapped on 15 lines wrapped on 15 lines wrapped on 15 lines wrapped on 15 lines wrapped on 15 lines wrapped on 15 lines wrapped on 15 lines wrapped on 15 lines wrapped on 15 lines wrapped on 15 lines wrapped on 15 lines wrapped on 15 lines wrapped on 15 lines wrapped on 15 lines wrapped on 15 lines wrapped on 15 lines wrapped on 15 lines wrapped on 15 lines wrapped on 15 lines wrapped on 15 lines wrapped on 15 lines wrapped on 15 lines wrapped on 15 lines wrapped on 15 lines wrapped on 15 lines wrapped on 15 lines wrapped on 15 lines wrapped on 15 lines wrapped on 15 lines wrapped on 15 lines wrapped on 15 lines wrapped on 15 lines wrapped on 15 lines wrapped on 15 lines wrapped on 15 lines wrapped on 15 lines wrapped on 15 lines wrapped on 15 lines wrapped on 15 lines wrapped on 15 lines
4
5
6
7
8
9
10
11
12
13
14
15 {
16
17
18
19
20
21
22
23
24
} 25
26
27
28
29
30 wrapped on 2 lines wrapped on 2 lines wrapped on 2 lines wrapped on 2 lines wrapped on 2 lines
31
32
33
34
35
36
37
38 wrapped on 2 lines wrapped on 2 lines wrapped on 2 lines wrapped on 2 lines wrapped on 2 lines
END
setline(1, lines)
feedkeys("23\<c-e>\<c-b>", 'nt')
EOF
)
With the current version of the plugin, the line with the text 24 is wrongly duplicated at the very bottom.
With the new version, the line with the text 24 is no longer wrongly duplicated; the last line displays the text 27 which is correct.
Test for the issue #3543:
vim -Nu NORC -S <(cat <<'EOF'
vim9script
set nocompatible
filetype plugin indent on
syntax enable
au BufReadPost * cursor(6, 7)
var lines =<< END
%{
figure(1); clf;
plot(1:npairskeep, 1./err.*log(1./err), ...
1:npairskeep, 0.5*(1:npairskeep));
figure(2); clf;
plot(1:npairskeep, mean(err));
epsilon = linspace(min(mean(err)),1);
mbound = 7./epsilon .* log(20./epsilon);
plot(1:npairskeep, mean(err)+2*std(err)/sqrt(ntrials), ...
1:npairskeep, mean(err)-2*std(err)/sqrt(ntrials), ...
1:npairskeep, err, ...
mbound, epsilon);
%}
END
writefile(lines, '/tmp/m.m')
syn on
sil e /tmp/m.m
feedkeys('1G2|jjjjjf)kkkkkjjjjj', 'nt')
EOF
)
With the current version of the plugin, the end of the block is no longer highlighted as a multiline comment.
With the new version, all the block remains properly highlighted as a multiline comment.
Test for the issue #4589:
vim -Nu NORC -S <(cat <<'EOF'
vim9script
var lines =<< trim END
a = 'blablabal'
# | put cursor above "{" or "}" in the "c = ..." line
b = { bla: 'bla' } # <- start cursor in this line
bla
c = '{}{}' # <- move cursor down to this line, as it is on "{" showmatch won't trigger
d.blabala()
c = '{}{}'
bla
d.blabala()
END
writefile(lines, '/tmp/py.py')
syn on
set showmatch
sil e /tmp/py.py
feedkeys('3G6|jj', 'nt')
EOF
)
With the current version of the plugin, the brace under the cursor and its matching pair are not highlighted.
With the new version, the braces are highlighted.
Test for #5638:
curl -Ls https://gist.githubusercontent.com/MRAAGH/11e7ea3dd460b393fc7e70d746b368f1/raw/03deddf9db2d248d5e7c56dedfb0c0d9d717bb42/vim_matchparen_bug >/tmp/file && vim -Nu NORC +'set lines=35 columns=166 scrolloff=0 | exe "norm! 97zb/} 2/e\<cr>" | call feedkeys("\<c-d>\<c-u>", "nt")' /tmp/file
If you can't reproduce the issue, temporarily reset the font to DejaVu Sans Mono with a font size of 12.
With the current version of the plugin, the line at the bottom of the window is wrongly displayed twice:
5 (this should be the last visible line in the window)
5 (this should be the last visible line in the window)
With the new version, the bottom line is only displayed once.
curl -Ls https://www.genivia.com/files/README.md >/tmp/md.md && vim -Nu NORC /tmp/md.md +'syn on | call feedkeys("2735GoThe char[", "nt")'
With the current version of the plugin, E363 is raised from s:Highlight_Matching_Pair():
Error detected while processing CursorMovedI Autocommands for "*"..function <SNR>18_Highlight_Matching_Pair[134]..CursorMovedI Autocommands for "*"..function <SNR>18_Highlight_Matching_Pair:
line 104:
E363: pattern uses more memory than 'maxmempattern'
With the new version of the plugin, E363 is still raised, but not from s:Highlight_Matching_Pair(); from the default markdown syntax plugin. In particular, notice the absence of a stacktrace.
BTW, #6777 is a duplicate of #5880.
Test for #7054:
vim -Nu NORC +'call feedkeys("ii\r{\r}\r{\r}\<c-\>\<c-n>gg3ddpuu", "nt")'
With the current version of the plugin, the brackets on the last 2 lines are wrongly highlighted.
With the new version of the plugin, the brackets are not highlighted.
Test for #7880:
vim -Nu NORC -S <(cat <<'EOF'
vim9script
syn on
var lines =<< trim END
print('Hi',
# ) (
'Bye')
END
writefile(lines, '/tmp/py.py')
sil e /tmp/py.py
feedkeys('2Gf)', 'nt')
EOF
)
With the current version of the plugin, the opening parenthesis on the first line is wrongly highlighted.
With the new version of the plugin, it is not highlighted.
https://github.com/vim/vim/pull/7985
—
You are receiving this because you are subscribed to this thread.
Reply to this email directly, view it on GitHub, or unsubscribe.![]()
I've just finished the refactoring, and there are no tests (that I know of) to check whether the new version introduces regressions. If I find issues in the next few days/weeks, I'll update the PR.
While refactoring the plugin, I've noticed that prop_remove() was much slower than matchdelete(). It was especially noticeable while moving the cursor with hl.
To make it faster, I've tried various things:
# don't use 'all: true'
prop_remove({type: 'matchparen'})
# limit the removal to the displayed lines
prop_remove({type: 'matchparen'}, line('w0'), line('w$'))
# limit the removal to the current line
prop_remove({type: 'matchparen'}, line('.'))
# limit the removal to the current line
prop_remove({type: 'matchparen'}, line('.'))
# clearing all text properties on current line
prop_clear(line('.'))
No matter what I did, it was always much slower.
Note that the current version of the plugin doesn't suffer from this, because it only invokes prop_remove() when it's really necessary.
Anyway, could prop_remove() be optimized internally?
Merging #7985 (3749fc1) into master (5f91e74) will decrease coverage by
86.68%.
The diff coverage is0.00%.
❗ Current head 3749fc1 differs from pull request most recent head 9393f0c. Consider uploading reports for the commit 9393f0c to get more accurate results
@@ Coverage Diff @@ ## master #7985 +/- ## =========================================== - Coverage 89.17% 2.49% -86.69% =========================================== Files 148 146 -2 Lines 165299 160182 -5117 =========================================== - Hits 147399 3990 -143409 - Misses 17900 156192 +138292
| Flag | Coverage Δ | |
|---|---|---|
| huge-clang-none | ? |
|
| huge-gcc-none | ? |
|
| huge-gcc-testgui | ? |
|
| huge-gcc-unittests | 2.49% <0.00%> (-0.01%) |
⬇️ |
Flags with carried forward coverage won't be shown. Click here to find out more.
| Impacted Files | Coverage Δ | |
|---|---|---|
| src/gui_beval.c | 0.00% <0.00%> (-61.64%) |
⬇️ |
| src/gui_gtk_x11.c | 0.45% <0.00%> (-62.23%) |
⬇️ |
| src/gui_xim.c | 0.00% <0.00%> (-23.75%) |
⬇️ |
| src/list.c | 2.43% <0.00%> (-92.65%) |
⬇️ |
| src/version.c | 0.86% <ø> (-91.48%) |
⬇️ |
| src/vim9compile.c | 0.00% <0.00%> (-94.04%) |
⬇️ |
| src/vim9script.c | 0.00% <0.00%> (-93.57%) |
⬇️ |
| src/vim9type.c | 0.00% <0.00%> (-95.61%) |
⬇️ |
| src/sha256.c | 0.00% <0.00%> (-97.96%) |
⬇️ |
| src/digraph.c | 0.00% <0.00%> (-97.78%) |
⬇️ |
| ... and 142 more |
Continue to review full report at Codecov.
Legend - Click here to learn more
Δ = absolute <relative> (impact),ø = not affected,? = missing data
Powered by Codecov. Last update 5f91e74...9393f0c. Read the comment docs.
I've just realized that Vim could be compiled without the +textprop feature. The plugin should fall back on matchaddpos() and matchdelete() in that case. I'll push a commit later.
@lacygoill wrote:
Anyway, could prop_remove() be optimized internally?
Do you have a simple script that illustrates prop_remove() being slow?
@lacygoill pushed 1 commit.
—
You are receiving this because you are subscribed to this thread.
@lacygoill pushed 1 commit.
—
You are receiving this because you are subscribed to this thread.
Do you have a simple script that illustrates prop_remove() being slow?
Sorry, I should have posted one. I'll try to find one later. In the meantime, here is a profile log which I got while working on a previous version of the plugin.
In particular, notice this line in the profiling of <SNR>38_RemoveHighlight():
320 0.151003 prop_remove({type: 'matchparen', all: true})
And the 2nd line in this block:
FUNCTIONS SORTED ON TOTAL TIME
count total (s) self (s) function
320 0.162068 0.007174 <SNR>38_Highlight()
320 0.151507 <SNR>38_RemoveHighlight()
9 0.005616 0.000074 <lambda>69()
1 0.004328 0.000711 <SNR>38_Toggle()
2 0.002636 <SNR>38_InStringOrComment()
312 0.001985 <SNR>38_GetOption()
1 0.001407 0.000005 <SNR>38_GetSkip()
1 0.000333 <SNR>38_Autocmds()
1 0.000075 <SNR>38_ParseMatchpairs()
<lambda>68()
<lambda>70()
<lambda>71()
<lambda>72()
<lambda>73()
As soon as I find a reproducible example, I'll post it here.
@lacygoill pushed 1 commit.
—
You are receiving this because you are subscribed to this thread.
@lacygoill pushed 1 commit.
—
You are receiving this because you are subscribed to this thread.
About using prop_remove() vs matchdelete: Text properties will be faster to display, but adding/removing involves updating the text storage. Adding/removing a match will be fast, but reduce display updates. What works best is hard to predict.
@lacygoill pushed 1 commit.
—
You are receiving this because you are subscribed to this thread.
@lacygoill pushed 1 commit.
—
You are receiving this because you are subscribed to this thread.
@lacygoill pushed 1 commit.
—
You are receiving this because you are subscribed to this thread.
@lacygoill pushed 1 commit.
—
You are receiving this because you are subscribed to this thread.
@lacygoill pushed 1 commit.
—
You are receiving this because you are subscribed to this thread.
@lacygoill pushed 1 commit.
—
You are receiving this because you are subscribed to this thread.
Do you have a simple script that illustrates prop_remove() being slow?
Sorry for the delay. Here is one:
vim -Nu NONE -S <(cat <<'EOF'
vim9script
prop_type_add('test', {highlight: 'ErrorMsg'})
au CursorMoved * Func()
def Func()
prop_remove({type: 'test', all: true})
enddef
repeat(['" ' .. repeat('x', 78)], 20)->setline(1)
syn on
set ft=vim
set ww=h,l
EOF
)
When I start Vim with this shell command (in an xterm terminal, 24 lines x 80 columns), then maintain the l key pressed for some time to scroll horizontally on the commented lines, my cpu load increases signficantly. It goes from between 2% and 3% to between 11% and 12%. In practice, it can cause some lag. That is, sometimes, the cursor is not displayed, which is distracting.
Here is another shell command, which uses matchdelete() instead:
vim -Nu NONE -S <(cat <<'EOF'
vim9script
au CursorMoved * Func()
w:id = matchadd('ErrorMsg', '^" xxx')
def Func()
sil! matchdelete(w:id)
enddef
repeat(['" ' .. repeat('x', 78)], 20)->setline(1)
syn on
set ft=vim
set ww=h,l
EOF
)
This time, my cpu load remains low and stable (i.e. between 2% and 3%). And I can't observe any noticeable lag.
Text properties will be faster to display [...]
Adding/removing a match will be fast, but reduce display updates.
Ah, so this explains why using text properties fixes a whole bunch of reported issues.
but adding/removing involves updating the text storage.
I see. There is still a way to obtain the desired effect here without the slowness. Remove the text property with prop_type_delete(), then immediately afterward re-install it with prop_type_add(). As soon as the text property is deleted, all the existing properties no longer cause any text to be highlighted. Unfortunately, they still exist (we can see them with prop_list()), so I don't use this method as I don't know if anything bad could happen when they pile up over time.
What works best is hard to predict.
It seems that text properties are just better anyway. We just need to not blindly clear them on every single motion. We must first check that there are such properties in the buffer, which is not too hard; a simple dictionary of boolean values seems enough.
@lacygoill pushed 1 commit.
—
You are receiving this because you are subscribed to this thread.
You call prop_remove() without any line number, which means it will go
over all lines in the buffer. If you restrict it to a range of lines it
will be much faster.
That's one of the first things I tried, and it didn't help. And it still doesn't help in this short snippet:
vim9script prop_type_add('test', {highlight: 'ErrorMsg'}) au CursorMoved * Func() def Func
()
prop_remove({type: 'test'}, line('.'), line('.'))
enddef repeat(['" ' .. repeat('x', 78)], 20)->setline(1) syn on set ft=vim set ww=h,l
set lines=24 columns=80
That's one of the first things I tried, and it didn't help. And it still doesn't help in this short snippet:
I'm not saying it doesn't help in the general case. I know it does. I've even pushed a recent commit to limit the removal of the text properties to the displayed lines, because I've made some tests showing that indeed it could help significantly.
I'm saying that it doesn't help in the (special?) case I posted. Maybe it has something to do with the syntax highlighting, I don't know.
Maybe it has something to do with the syntax highlighting, I don't know.
Yes, it's an interaction with the syntax highlighting. Because the issue disappears when it's disabled with :syn off. And it re-appears with :syn on. Maybe it's expected, I don't know. But there is no such issue with matches removed with matchdelete().
If I profile with gprof this command and press l many times...
vim -Nu NONE -S <(cat <<'EOF'
vim9script
prop_type_add('test', {highlight: 'ErrorMsg'})
au CursorMoved * Func()
def Func()
prop_remove({type: 'test', all: true})
enddef
repeat(['" ' .. repeat('x', 78)], 20)->setline(1)
syn on
set ft=vim
set ww=h,l
EOF
)
I get:
Flat profile:
Each sample counts as 0.01 seconds.
% cumulative self self total
time seconds seconds calls s/call s/call name
29.29 0.82 0.82 24517280 0.00 0.00 addstate
22.50 1.45 0.63 312806 0.00 0.00 nfa_regmatch
6.61 1.64 0.19 59872126 0.00 0.00 utfc_ptr2len
6.43 1.82 0.18 40841835 0.00 0.00 copy_sub
3.93 1.93 0.11 575100 0.00 0.00 regmatch
3.21 2.02 0.09 10089100 0.00 0.00 mb_get_class_buf
2.86 2.10 0.08 11651103 0.00 0.00 vim_iswordc_buf
2.50 2.17 0.07 27298532 0.00 0.00 utf_ptr2char
2.50 2.24 0.07 515159 0.00 0.00 vim_strchr
2.14 2.30 0.06 12079596 0.00 0.00 regnext
2.14 2.36 0.06 754305 0.00 0.00 nfa_regexec_both
1.43 2.40 0.04 4984200 0.00 0.00 reg_prev_class
1.43 2.44 0.04 575100 0.00 0.00 syn_current_attr
1.07 2.47 0.03 46855576 0.00 0.00 fast_breakcheck
1.07 2.50 0.03 18906790 0.00 0.00 line_breakcheck
0.71 2.52 0.02 5175900 0.00 0.00 reg_save
I also get something similar with valgrind --tool=callgrind ./vim ….
:syntime on … syntime report gives:
TOTAL COUNT MATCH SLOWEST AVERAGE NAME PATTERN
0.749363 6460 0 0.000431 0.000116 vimFunc \%(\%([sSgGbBwWtTlL]:\|<[sS][iI][dD]>\)\=\%(\w\+\.\)*\I[a-zA-Z0-9_.]*\)\ze\s*(
0.326026 6460 0 0.000181 0.000050 vimOper \%#=1\(==\|!=\|>=\|<=\|=\~\|!\~\|>\|<\|=\)[?#]\{0,2}
0.324723 6460 0 0.000180 0.000050 vimNotation \(\\\|<lt>\)\=<C-R>[0-9a-z"%#:.\-=]
0.291503 6460 0 0.000128 0.000045 vimSubst \(:\+\s*\|^\s*\||\s*\)\<\%(\<s\%[ubstitute]\>\|\<sm\%[agic]\>\|\<sno\%[magic]\>\)[:#[:alpha:]]\@!
0.265514 6460 6460 0.000143 0.000041 vimLineComment ^[ \t:]*".*$
0.263650 6460 0 0.000138 0.000041 vimNotFunc \<if\>\|\<el\%[seif]\>\|\<return\>\|\<while\>
0.256317 6460 0 0.000109 0.000040 vimNumber \%(^\|\A\)\zs#\x\{6}
0.240585 6460 0 0.000150 0.000037 vimSpecFile #\d\+\|[#%]<\>
0.228740 6460 0 0.000077 0.000035 vimOperParen #\={
0.221684 6460 0 0.000154 0.000034 vimAutoCmdMod \(++\)\=\(once\|nested\)
0.202926 6460 0 0.000107 0.000031 vimRange [`'][a-zA-Z0-9],[`'][a-zA-Z0-9]
0.181361 6460 6460 0.000124 0.000028 vimIsCommand \<\h\w*\>
0.157176 6460 0 0.000155 0.000024 vimRegister [^,;[{: \t]\zs"[a-zA-Z0-9.%#:_\-/]\ze[^a-zA-Z_":0-9]
0.150993 6460 0 0.000116 0.000023 vimOper ||\|&&\|[-+.!]
0.141072 6460 0 0.000057 0.000022 vimInsert ^[: \t]*\(\d\+\(,\d\+\)\=\)\=starti\%[nsert]$
0.124664 6460 0 0.000067 0.000019 vimSubst \(:\+\s*\|^\s*\)s\ze#.\{-}#.\{-}#
0.116548 6460 0 0.000052 0.000018 vimCommentTitle "\s*\%([sS]:\|\h\w*#\)\=\u\w*\(\s\+\u\w*\)*:
0.105377 6460 0 0.000116 0.000016 vimString [^(,]'[^']\{-}\zs'
0.103306 6460 0 0.000087 0.000016 vimCommentString \S\s\+"
0.089008 6460 0 0.000047 0.000014 vimFunction \<\(fu\%[nction]\|def\)!\=\s\+\%(<[sS][iI][dD]>\|[sSgGbBwWtTlL]:\)\=\%(\i\|[#.]\|{.\{-1,}}\)*\ze\s*(
CPU is about 43% on my machine when I keep pressing l.
Unclear at first sight if there is anything to optimize here.
Thank you for the logs. I suspect that Vim recomputes the syntax items for all the displayed text whenever a text property is added or removed. Which makes sense. What I find weird is: why doing that even when prop_remove() failed to remove any property?
@lacygoill wrote:
I suspect that Vim recomputes the syntax items for all the
displayed text whenever a text property is added or removed.
Which makes sense. What I find weird is: why doing that
even when prop_remove() failed to remove any property?
Yes, I think you're right. I don't understand the code of text
properties well, but I think this fixes the performance issue:
Can you try it?
diff --git a/src/textprop.c b/src/textprop.c
index b6cae70a8..e74c13849 100644
--- a/src/textprop.c
+++ b/src/textprop.c
@@ -809,6 +809,7 @@ f_prop_remove(typval_T *argvars, typval_T *rettv)
int id = -1;
int type_id = -1;
int both;
+ int is_removed = FALSE;
rettv->vval.v_number = 0;
if (argvars[0].v_type != VAR_DICT || argvars[0].vval.v_dict == NULL)
@@ -889,6 +890,7 @@ f_prop_remove(typval_T *argvars, typval_T *rettv)
if (both ? textprop.tp_id == id && textprop.tp_type == type_id
: textprop.tp_id == id || textprop.tp_type == type_id)
{
+ is_removed = TRUE;
if (!(buf->b_ml.ml_flags & ML_LINE_DIRTY))
{
char_u *newptr = alloc(buf->b_ml.ml_line_len);
@@ -920,7 +922,8 @@ f_prop_remove(typval_T *argvars, typval_T *rettv)
}
}
}
- redraw_buf_later(buf, NOT_VALID);
+ if (is_removed)
+ redraw_buf_later(buf, NOT_VALID);
}
For me, CPU goes from 43% to 0.7% when pressing l continuously.
Thank you for the patch:
diff --git a/src/textprop.c b/src/textprop.c index b6cae70a8..e74c13849 100644 --- a/src/textprop.c +++ b/src/textprop.c @@ -809,6 +809,7 @@ f_prop_remove(typval_T *argvars, typval_T *rettv) int id = -1; int type_id = -1; int both; + int is_removed = FALSE; rettv->vval.v_number = 0; if (argvars[0].v_type != VAR_DICT || argvars[0].vval.v_dict == NULL) @@ -889,6 +890,7 @@ f_prop_remove(typval_T *argvars, typval_T *rettv) if (both ? textprop.tp_id == id && textprop.tp_type == type_id : textprop.tp_id == id || textprop.tp_type == type_id) { + is_removed = TRUE; if (!(buf->b_ml.ml_flags & ML_LINE_DIRTY)) { char_u *newptr = alloc(buf->b_ml.ml_line_len); @@ -920,7 +922,8 @@ f_prop_remove(typval_T *argvars, typval_T *rettv) } } } - redraw_buf_later(buf, NOT_VALID); + if (is_removed) + redraw_buf_later(buf, NOT_VALID);
} /*
Yes, the difference is huge. Vim is much more responsive, and the cpu load remains low.
@lacygoill wrote:
Yes, the difference is huge. Vim is much more responsive, and the cpu load remains low.
Good.
The same optimization can also be done for prop_clear() (not sure how often
it helps in practice). Updated patch:
diff --git a/src/textprop.c b/src/textprop.c index b6cae70a8..f2b21ca3d 100644 --- a/src/textprop.c +++ b/src/textprop.c @@ -535,6 +535,7 @@ f_prop_clear(typval_T *argvars, typval_T *rettv UNUSED) linenr_T end = start; linenr_T lnum; buf_T *buf = curbuf; + int is_cleared = FALSE; if (argvars[1].v_type != VAR_UNKNOWN) { @@ -562,6 +563,7 @@ f_prop_clear(typval_T *argvars, typval_T *rettv UNUSED) len = STRLEN(text) + 1; if ((size_t)buf->b_ml.ml_line_len > len) { + is_cleared = TRUE; if (!(buf->b_ml.ml_flags & ML_LINE_DIRTY)) { char_u *newtext = vim_strsave(text); @@ -575,7 +577,8 @@ f_prop_clear(typval_T *argvars, typval_T *rettv UNUSED) buf->b_ml.ml_line_len = (int)len; } } - redraw_buf_later(buf, NOT_VALID); + if (is_cleared) + redraw_buf_later(buf, NOT_VALID); } /* @@ -809,6 +812,7 @@ f_prop_remove(typval_T *argvars, typval_T *rettv) int id = -1; int type_id = -1; int both;
+ int is_removed = FALSE;
rettv->vval.v_number = 0;
if (argvars[0].v_type != VAR_DICT || argvars[0].vval.v_dict == NULL)
@@ -889,6 +893,7 @@ f_prop_remove(typval_T *argvars, typval_T *rettv)
if (both ? textprop.tp_id == id && textprop.tp_type == type_id
: textprop.tp_id == id || textprop.tp_type == type_id)
{
+ is_removed = TRUE;
if (!(buf->b_ml.ml_flags & ML_LINE_DIRTY))
{
char_u *newptr = alloc(buf->b_ml.ml_line_len);
@@ -920,7 +925,8 @@ f_prop_remove(typval_T *argvars, typval_T *rettv)
}
}
}
- redraw_buf_later(buf, NOT_VALID); + if (is_removed) + redraw_buf_later(buf, NOT_VALID); } /*
—
@lacygoill pushed 1 commit.
—
You are receiving this because you are subscribed to this thread.
—
You are receiving this because you are subscribed to this thread.
I just found the same and made a patch. We can use the return value
instead of adding a new flag.
Thank you for the patch. As pointed out by @dpelle, the issue persists with prop_clear():
diff --git a/src/textprop.c b/src/textprop.c index d6782342d..6b7106590 100644 --- a/src/textprop.c +++ b/src/textprop.c @@ -575,7 +575,8 @@ f_prop_clear(typval_T *argvars, typval_T *rettv UNUSED) buf->b_ml.ml_line_len = (int)len; } } - redraw_buf_later(buf, NOT_VALID); + if (rettv->vval.v_number > 0) + redraw_buf_later(buf, NOT_VALID); } /*
@lacygoill pushed 1 commit.
—
You are receiving this because you are subscribed to this thread.
@cwfoo commented on this pull request.
In runtime/plugin/matchparen.vim:
> else
- if has("byte_offset") && has("syntax_items") && &smc > 0
- let stopbyte = max([1, line2byte(".") + col(".") - &smc * 2])
- let stopline = max([top_viewable, byte2line(stopbyte)])
+ # If evaluating the expression determines that the cursor is
+ # currently in a text with some specific syntax type (like a string
+ # or a comment), then we want searchpairpos() to find a pair within
+ # a text of similar type; i.e. we want to ignore a pair of different
+ # syntax type.
+ if InStringOrComment()
+ return (): bool => !InStringOrComment()
Shouldn't this be something like:
if InString()
return (): bool => !InString()
elseif InComment()
return (): bool => !InComment()
else
return (): bool => InStringOrComment()
endif
Are there occasions where one would like parentheses in comments to match with parentheses in strings?
—
You are receiving this because you are subscribed to this thread.
> - let stopline = min([bottom_viewable, byte2line(stopbyte)])
- else
- let stopline = min([bottom_viewable, c_lnum + 100])
- endif
- let stoplinebottom = stopline
+enddef
+
+def InStringOrComment(): bool #{{{2
+ # Expression which detects whether the current cursor position is in certain
+ # syntax types (string, comment, etc.); evaluated inside lambda passed as
+ # skip argument to searchpairpos().
+ return synstack('.', col('.'))
+ ->mapnew((_, v: number): string => synIDattr(v, 'name'))
+ # We match "escape" and "symbol" for special items, such as
+ # lispEscapeSpecial or lispBarSymbol.
+ ->match('\cstring\|character\|singlequote\|escape\|symbol\|comment') >= 0
Doesn't this iterate through the entire list every time, instead of stopping when the first match is found?
@lacygoill commented on this pull request.
In runtime/plugin/matchparen.vim:
> else
- if has("byte_offset") && has("syntax_items") && &smc > 0
- let stopbyte = max([1, line2byte(".") + col(".") - &smc * 2])
- let stopline = max([top_viewable, byte2line(stopbyte)])
+ # If evaluating the expression determines that the cursor is
+ # currently in a text with some specific syntax type (like a string
+ # or a comment), then we want searchpairpos() to find a pair within
+ # a text of similar type; i.e. we want to ignore a pair of different
+ # syntax type.
+ if InStringOrComment()
+ return (): bool => !InStringOrComment()
Yes, I noticed that too. The issue is that I don't know what kind of texts the original tokens were supposed to match:
\ '''v:val =~? "string\\|character\\|singlequote\\|escape\\symbol\\|comment"''))'
What does each of these match:
charactersinglequoteescapesymbolSome part of a comment? Some part of a string? Something else?
Since I don't know, I've just kept the logic of the original plugin which doesn't handle them differently. With the exception of the test inversion which was wrong.
> - let stopline = min([bottom_viewable, byte2line(stopbyte)])
- else
- let stopline = min([bottom_viewable, c_lnum + 100])
- endif
- let stoplinebottom = stopline
+enddef
+
+def InStringOrComment(): bool #{{{2
+ # Expression which detects whether the current cursor position is in certain
+ # syntax types (string, comment, etc.); evaluated inside lambda passed as
+ # skip argument to searchpairpos().
+ return synstack('.', col('.'))
+ ->mapnew((_, v: number): string => synIDattr(v, 'name'))
+ # We match "escape" and "symbol" for special items, such as
+ # lispEscapeSpecial or lispBarSymbol.
+ ->match('\cstring\|character\|singlequote\|escape\|symbol\|comment') >= 0
match() returns the index of the first item where the pattern matches:
When {expr} is a |List| then this returns the index of the
first item where {pat} matches. Each item is used as a
To get the index of the first item, it doesn't need to iterate over the entire list (unless the pattern never matches).
Here is a simple test:
vim9script var size: number = 10 var freq: number = 100'000 var hugelist: list<string> = repeat(['stack'], 100) + ['needle'] + repeat(['stack'], size) def Test() for i in range(freq) eval match(hugelist, 'needle') endfor enddef defcompile var time = reltime() Test() (reltime(time)->reltimestr()->matchstr('.*\..\{,3}') .. ' seconds to run Test()')->setline(1)
size controls the size of the list.
freq controls how frequently the function is invoked.
On my machine, this snippet reports that the function takes about 0.6s, no matter the value of size.
That is, even if I increase size from 10 to 100000, the function still takes 0.6s. If match() was iterating over the entire list, it would take more time as the list grows in size; but it does not.
> else
- if has("byte_offset") && has("syntax_items") && &smc > 0
- let stopbyte = max([1, line2byte(".") + col(".") - &smc * 2])
- let stopline = max([top_viewable, byte2line(stopbyte)])
+ # If evaluating the expression determines that the cursor is
+ # currently in a text with some specific syntax type (like a string
+ # or a comment), then we want searchpairpos() to find a pair within
+ # a text of similar type; i.e. we want to ignore a pair of different
+ # syntax type.
+ if InStringOrComment()
+ return (): bool => !InStringOrComment()
Are there occasions where one would want parentheses in comments to match with parentheses in strings?
Also, the suggested code wouldn't always work as expected:
elseif InComment()
return (): bool => !InComment()
Suppose the cursor is on the opening parenthesis on this comment:
# (aaa "bbb)" ccc
InComment() is true.
searchpairpos() finds the closing parenthesis.
On the closing parenthesis, InComment() is still true, !InComment() is false, the skip expression is false, and searchpairpos() does not discard the match.
The end result is that the plugin highlights the closing parenthesis. But that's not what you wanted; your comment seems to imply that you wanted to ignore a match in a string if the cursor was on a paren in a comment.
What makes things complex is the possibility for syntax items to be nested; like you can have a string highlighted as a string inside a comment.
I'm not saying the current logic can't be improved. I haven't thought much about it, as I couldn't give more time to this refactoring. But if you have a specific patch in mind, let me know.
@cwfoo commented on this pull request.
In runtime/plugin/matchparen.vim:
> - let stopline = min([bottom_viewable, byte2line(stopbyte)])
- else
- let stopline = min([bottom_viewable, c_lnum + 100])
- endif
- let stoplinebottom = stopline
+enddef
+
+def InStringOrComment(): bool #{{{2
+ # Expression which detects whether the current cursor position is in certain
+ # syntax types (string, comment, etc.); evaluated inside lambda passed as
+ # skip argument to searchpairpos().
+ return synstack('.', col('.'))
+ ->mapnew((_, v: number): string => synIDattr(v, 'name'))
+ # We match "escape" and "symbol" for special items, such as
+ # lispEscapeSpecial or lispBarSymbol.
+ ->match('\cstring\|character\|singlequote\|escape\|symbol\|comment') >= 0
Sorry, I've highlighted the wrong line. I should have highlighted the mapnew() instead of match().
Regarding the mapnew() on line 244:
It seems that synIDattr() is called on every item returned by synstack('.', col('.')). Shouldn't synIDattr() be called as we go through the list instead of calling it on every single item in the list? In other words, shouldn't it be something like this:
function InStringOrComment()
for synID in synstack(line('.'), col('.'))
if synIDattr(synID, 'name') =~? '\cstring\|character\|singlequote\|escape\|symbol\|comment'
return 1
endif
endfor
return 0
endfunction
This way, the number of calls to synIDattr() is minimized.
@lacygoill pushed 1 commit.
—
You are receiving this because you are subscribed to this thread.
@lacygoill commented on this pull request.
In runtime/plugin/matchparen.vim:
> - let stopline = min([bottom_viewable, byte2line(stopbyte)])
- else
- let stopline = min([bottom_viewable, c_lnum + 100])
- endif
- let stoplinebottom = stopline
+enddef
+
+def InStringOrComment(): bool #{{{2
+ # Expression which detects whether the current cursor position is in certain
+ # syntax types (string, comment, etc.); evaluated inside lambda passed as
+ # skip argument to searchpairpos().
+ return synstack('.', col('.'))
+ ->mapnew((_, v: number): string => synIDattr(v, 'name'))
+ # We match "escape" and "symbol" for special items, such as
+ # lispEscapeSpecial or lispBarSymbol.
+ ->match('\cstring\|character\|singlequote\|escape\|symbol\|comment') >= 0
I didn't notice a speed increase during a simple quick test, but I guess you're right, so I've pushed a commit which refactors the mapnew() into a :for loop.
—
You are receiving this because you are subscribed to this thread.
@lacygoill pushed 1 commit.
—
You are receiving this because you are subscribed to this thread.
The current version is still slower than the original one in some cases.
For example, if I copy the script in a temporary file, then write this line in the middle:
return max([5, min([winheight(0), winnr('#')->winheight() / 2])])
That is, right above this line (there are 2 of them; the first one):
if before > 0
Then, if I enter insert mode right before the last paren:
return max([5, min([winheight(0), winnr('#')->winheight() / 2])])
^
And keep pressing i, there is some noticeable lag, which doesn't occur when using the original plugin version.
I know that the lag comes from 2 things:
prop_add() which is slower than matchadd()Skip expression which is slower than a legacy eval stringIt mainly comes from the latter. But Skip is a reference to a compiled function. Why is a compiled function still slower than an eval string in legacy Vim script?
—
You are receiving this because you are subscribed to this thread.
—
Sorry, I made a mistake. I think the skip expression is fine. It was not when I found out the issue a few months ago. But at the time, I was using a binary without optimizations (to debug a crash), and I didn't have these patches:
But there is still some lag when inserting characters in front of a parenthesis. I think the issue is due to prop_add(), because the lag disappears when I comment it out.
I've done some profiling with :prof. prop_add() by itself is actually faster than matchaddpos(). I think the issue is that prop_add() makes Vim redraw the screen immediately, causing the syntax to be recomputed for all the displayed text. This is confirmed by the fact that the lag also disappears when the syntax is temporarily disabled with :syn off.
I don't think this can be fixed in Vim. The fact that Vim redraws more eagerly with prop_add() than with matchaddpos() is a good thing; it fixes a bunch of issues where the highlighting is currently wrong.
The only workaround I can think of, is a timer. That is: wait a short amount of time before updating the highlighting again. Originally, I did implement such a mechanism, but I reverted the feature; it made the code more complex, and I didn't like how the highlighting seemed to blink when inserting characters in front of a parenthesis.
—
For some reason, the lag is still there, and I know it's caused by an interaction between prop_add() and the syntax highlighting. According to :syntime, the overall time spent on syntax is about the same. However, the number of times a syntax rule is used has been cut almost in half. I guess it means that – in this particular case – the most costly syntax computations are on the redrawn line, where we insert text in front of a mix of parentheses...
Also, out of the 7 "tests" included in the OP, 2 are now broken by the patch 8.2.2860. The one checking the fix for the issue #1532, and the one checking the fix for the issue #5638.
I don't think it can be avoided, and it's not that important if it's the price to pay to have better performance.
I'll see if a timer can help further.
For some reason, the lag is still there, and I know it's caused by an interaction between prop_add() and the syntax highlighting.
Actually, I think the issue comes from an interaction between prop_remove() and the syntax highlighting. Could the optimization implemented in 8.2.2860 be applied to prop_remove() too?
Could the optimization implemented in 8.2.2860 be applied to prop_remove() too?
Never mind. That would not help, since we need to remove the properties across all the lines displayed in the window.
—
The last patches (8.2.2862, 8.2.2863) – probably combined with 8.2.2860 – have totally removed the lag; thank you very much.
@lacygoill pushed 1 commit.
—
You are receiving this because you are subscribed to this thread.
@lacygoill pushed 1 commit.
—
View it on GitHub.
Triage notifications on the go with GitHub Mobile for iOS or Android.
You are receiving this because you are subscribed to this thread.![]()