Syntax highlight flicker when same buffer open in multiple windows at different scroll positions
Problem
Hi,
a few days ago I got this weird and hard to diagnose behavior, that the syntax highlighting (I think it's tree sitter, not LSP) starts to flicker really fast (as in with every draw call). Sometimes this also blocks neovim from reacting to user inputs, even though the CPU usage is pretty low.
Steps to reproduce
(I will try and create a minimal repro later, right now I'm a bit pressed for time.)
- have treesitter parser for
vueandtypescriptorjavascriptinstalled - open a
.vuefile large enough to fill the editor viewport at least twice, vertically (ideally with the template section and the script section each taking up more space than the viewport) - split the buffer into a new window
- scroll the second window, so that one window shows one injected language section and the other shows the other injected language section
- observe flickering (do not do this, if you are suffering from epilepsy)
Expected behavior
The highlighting should not flicker and not block neovim from reacting at all.
Nvim version (nvim -v)
NVIM v0.11.0-dev-1860+g877f3b7288
Vim (not Nvim) behaves the same?
not tested
Operating system/version
Windows 11 24H2 26100.3194
Terminal name/version
wezterm 20240203-110809-5046fc22
$TERM environment variable
N/A
Installation
winget (nightly neovim)
Here is a recording of the problem. Again: You might not want to watch this, if you are suffering from epilepsy. https://github.com/user-attachments/assets/9ad7898a-1096-49a1-997e-fa5b9170297a
The flickering is even more pronounced when I run neovide instead, due to a higher frame rate.
Need repro steps starting with nvim --clean .
This is likely because LanguageTree._processed_injection_range only holds a single range, and each parse attempt from the highlighter (for different windows) sets the range to that specific window's range. Maybe to allow multiple ranges, this property could be a list of ranges, or maybe something smarter like a tree that allows for efficient overlap queries
@ribru17 do you still need me to come up with nvim --clean repro steps? You already seem to have an idea where to look.
Maybe @justinmk would still prefer one for testing purposes👍I can try and reproduce without, so no rush from my end
Edit: even just a minimal vue file that caused this would help
Sure, I got it to trigger with the following file. Just split the window and scroll one window to the top and the other to the bottom of the file. Though side note: It triggers less often as with the actual files I'm working on. If this file isn't enough, I can see about cleaning one of the work files of proprietary information and posting that as well.
In order to add nvim --clean repro steps I first need to figure out how to install the vue parser without installing nvim-treesitter first.
<script setup lang="ts">
import HelloWorld from "./components/HelloWorld.vue";
import TheWelcome from "./components/TheWelcome.vue";
const foo = [];
foo.push(1);
foo.push(1);
foo.push(1);
foo.push(1);
foo.push(1);
foo.push(1);
foo.push(1);
foo.push(1);
foo.push(1);
foo.push(1);
foo.push(1);
foo.push(1);
foo.push(1);
foo.push(1);
foo.push(1);
foo.push(1);
foo.push(1);
foo.push(1);
foo.push(1);
foo.push(1);
foo.push(1);
foo.push(1);
foo.push(1);
foo.push(1);
foo.push(1);
foo.push(1);
foo.push(1);
foo.push(1);
foo.push(1);
foo.push(1);
foo.push(1);
foo.push(1);
foo.push(1);
foo.push(1);
foo.push(1);
foo.push(1);
foo.push(1);
foo.push(1);
foo.push(1);
foo.push(1);
foo.push(1);
foo.push(1);
foo.push(1);
foo.push(1);
foo.push(1);
foo.push(1);
foo.push(1);
foo.push(1);
foo.push(1);
foo.push(1);
foo.push(1);
foo.push(1);
foo.push(1);
foo.push(1);
foo.push(1);
foo.push(1);
foo.push(1);
foo.push(1);
foo.push(1);
foo.push(1);
foo.push(1);
foo.push(1);
foo.push(1);
foo.push(1);
foo.push(1);
foo.push(1);
foo.push(1);
foo.push(1);
foo.push(1);
foo.push(1);
foo.push(1);
foo.push(1);
foo.push(1);
foo.push(1);
foo.push(1);
foo.push(1);
foo.push(1);
foo.push(1);
foo.push(1);
foo.push(1);
foo.push(1);
foo.push(1);
foo.push(1);
foo.push(1);
foo.push(1);
foo.push(1);
foo.push(1);
foo.push(1);
foo.push(1);
foo.push(1);
foo.push(1);
foo.push(1);
foo.push(1);
foo.push(1);
foo.push(1);
foo.push(1);
foo.push(1);
foo.push(1);
foo.push(1);
foo.push(1);
</script>
<template>
<header>
<img
alt="Vue logo"
class="logo"
src="./assets/logo.svg"
width="125"
height="125"
/>
<div class="wrapper">
<HelloWorld msg="You did it!" />
</div>
</header>
<main>
<TheWelcome />
</main>
</template>
<style scoped>
header {
line-height: 1.5;
}
.logo {
display: block;
margin: 0 auto 2rem;
}
@media (min-width: 1024px) {
header {
display: flex;
place-items: center;
padding-right: calc(var(--section-gap) / 2);
}
.logo {
margin: 0 2rem 0 0;
}
header .wrapper {
display: flex;
place-items: flex-start;
flex-wrap: wrap;
}
}
</style>
I can repro this exactly. Seeing with kickstart.nvim installed, though usually when scrolling up and down with the keyboard and only with window splits. Even when neovim is unfocussed and idle, the flicker still occasionally occurs in the lower pane. During the flicker text loses its colors and styling (bold, italic etc).
So far I've only seen this, at least obviously, with .vue SFC files.
I've noticed that occasionally the flicker can 'freeze' in the uncolored/unformatted state when the which-key popup is waiting for input.
Further tests carried out:
- running
nvim --cleanthe problem disappears. With --clean am I correct in assuming that treesitter is not used and neovim defers back to regex based syntax parsing? - disabling all other plugins except treesitter and the problem remains
- leaving kickstart.nvim as upstream and disabling treesitter alone the problem disappears
- with treesitter enabled, removing the nightly build of neovim and replacing with 10.4 release build, the problem disappears
It seems the issue is a change since the 10.4 release that's causing an issue with treesitter either only or mostly visible with .vue SFC files.
Running neovim latest nightly on windows 10, testing with vanilla kickstart.nvim. Tested with Wezterm and latest MS Terminal preview (same result)
Come to think of it I've only seen this issue appear over the last few weeks, and @ribru17 has made a number of commits eg. 0c9c140f91c97dfacf8648a5fee90b06d8c50bbc cbad2c662873bf791309505418407c804db219b7 to refactor/improve treesitter over that time. I don't have a nightly from a few weeks ago and don't have a build setup in windows, perhaps someone could wind back a few weeks to pinpoint? Everything is fine at neovim 10.4.
Yeah, this would be due to #32482
I think a good way to fix this would be to store all ranges for which injections have been processed in an interval tree. We can reset the tree as we do now, and when we run the injection query over a new region we can add that range to the tree for efficient lookups in future is_valid() calls. Can make use of the data structure in #32692
I'm using vim.g._ts_force_sync_parsing = true as a temporary workaround.
@ribru17 I'm left wondering why this problem is particularly visible with vue files. I'm often splitting via Ctrl-W ] and only really see this issue working with them. Is this more visible in the case where different syntaxes are applied, eg. in vue files the template section being html flavoured but the script section being js/ts?
Good question, I think in general this is more likely to happen if the parse attempts take place across multiple event loop iterations (are slower). Having a lot of injections definitely doesn't help, and it may be the case that the vue parser has a lot of states which would make it slower
Although I experience the same issue as described above, with markdown and injected syntaxes (i.e. flickering while scrolling, using splits that show different parts of a document), I see flickering also while typing (even without splits). Not sure if the cause is the same. Reproduction steps:
- run
nvim --clean :set ft=markdown:lua vim.treesitter.start()- paste the markdown content below, to the empty buffer
- duplicate the markdown content a few times (I can see the flickering easily, when the buffer contains ~300 lines)
- start typing at the bottom a new markdown paragraph
Notes:
- the flickering is more visible when opening the same buffer, in a split window
:lua vim.g._ts_force_sync_parsing = trueprevents the flickering in this case as well- nvim:
v0.11.0-dev-1876+g86046c5a31, tree-sitter:v0.25.2
Sample content:
## Title
Lorem ipsum odor amet, consectetuer adipiscing elit. Sem pulvinar curae a eleifend feugiat. Potenti urna magna cursus placerat conubia gravida egestas nibh. Efficitur felis inceptos pulvinar; dapibus primis mi vivamus pharetra. Hendrerit molestie vehicula varius iaculis nisi orci porta convallis torquent. Mollis tortor fringilla non aliquet lobortis facilisi. Bibendum habitant urna quam donec posuere dis primis ad.
```lua
function greet(name)
print("Hello, " .. name .. "!")
end
greet("Alice")
person = {
name = "Bob",
age = 25,
}
print("Name: " .. person.name)
print("Age: " .. person.age)
local sum = 0
for i = 1, 5 do
sum = sum + i
end
print("Sum of numbers from 1 to 5: " .. sum)
local counter = 1
while counter <= 5 do
print("Counter value: " .. counter)
counter = counter + 1
end
```
I'm using
vim.g._ts_force_sync_parsing = trueas a temporary workaround.
I can confirm that this works as a workaround for me as well.
I want to up this issue, because it has repro steps and :lua vim.g._ts_force_sync_parsing = true helps.
Issue can be encountered on :
NVIM v0.11.0-dev-2044+gaafbd442b2
Build type: RelWithDebInfo
LuaJIT 2.1.1741730670
Run "nvim -V1 -v" for more info
I experience flicker of the current line while typing. So far it weirdly only seems to happen on one specific file. Maybe related to file size? Though I tested another file of a similar size, and the flicker did not show up.
Typing on a comment line flickers the whole line. On some other lines there is only partial flicker within for example, a string. The whole file also flickers at start when opening nvim.
If I remove 90% of the file's contents, the flickering seems to no longer occur.
File: theme.txt
Github doesn't allow adding the file with a .lua extension, so the file probably needs to be renamed manually to get treesitter to highlight it as lua.
nvim --version:
NVIM v0.11.0-dev-2064+ge0cd8cfba4
Build type: RelWithDebInfo
LuaJIT 2.1.1741730670
Run "nvim -V1 -v" for more info
Edit: This occurs with a single window/buffer.
I'm seeing a very similar issue with Neovim v0.11. I use https://github.com/crispgm/nvim-go and issuing a :GoFormat with treesitter syntax highlighting enabled on a large .go file causes a "flash of unhighlighted text" as the file is formatted and reloaded.
Disabling async parsing fixes the issue for me:
vim.g._ts_force_sync_parsing = true
The code for the formatting function of the nvim-go plugin is here: https://github.com/crispgm/nvim-go/blob/main/lua/go/format.lua
If there is some improvement that could be made to the plugin code to avoid the highlighting flicker while keeping treesitter highlighting async I'd be interested to hear it.
If there is some improvement that could be made to the plugin code to avoid the highlighting flicker while keeping treesitter highlighting async I'd be interested to hear it.
Not sure about plugins specifically, but the core solution to prevent flicker would be to apply a double-buffering approach in the highlighter. This prevents flicker while typing (flicker for multiple windows still needs a separate parser-level change)
I have a commit which eliminates flicker for the first scenario but it requires a decent refactor, so it may take some time before it is ready
This new issue I created, now marked as duplicate, contains more details and a longer recording. Please have a look: https://github.com/neovim/neovim/issues/33139
Note that my case is completely unrelated to moving around. I do not think it is even related to injection, my issue has been observed in JSON for instance. But it is very notable in Markdown.
I guess my issue is closer to https://github.com/neovim/neovim/issues/32660#issuecomment-2743503801 @Aumnescio, can you please compare? 🙏
I confirm it is fixed by disabling async parsing: https://github.com/neovim/neovim/issues/32660#issuecomment-2756897985 Sadly :(
Hope it helps!
Will be fixed by https://github.com/neovim/neovim/pull/33145
Fixed by https://github.com/neovim/neovim/pull/36503