Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

bug: notifier causes cursor flickering (especially with lsp progress - with possible solution) #613

Closed
4 tasks done
synic opened this issue Jan 18, 2025 · 5 comments · Fixed by #424
Closed
4 tasks done
Labels
bug Something isn't working

Comments

@synic
Copy link

synic commented Jan 18, 2025

Did you check docs and existing issues?

  • I have read all the snacks.nvim docs
  • I have updated the plugin to the latest version before submitting this issue
  • I have searched the existing issues of snacks.nvim
  • I have searched the existing issues of plugins related to this issue

Neovim version (nvim -v)

v0.10.3

Operating system/version

MacOS 15.2

Describe the bug

Quick updates to notification windows causes the cursor to flicker (for instance, using the LSP progress update code, when updates are frequent, the cursor flickers quite noticeably)

Steps To Reproduce

Enable snacks notifier, use the LSP progress code, like below. Open a file where there are a lot of LSP progress updates, and you will see cursor flickering.

---@type table<number, {token:lsp.ProgressToken, msg:string, done:boolean}[]>
local progress = vim.defaulttable()
vim.api.nvim_create_autocmd("LspProgress", {
  ---@param ev {data: {client_id: integer, params: lsp.ProgressParams}}
  callback = function(ev)
    local client = vim.lsp.get_client_by_id(ev.data.client_id)
    local value = ev.data.params.value --[[@as {percentage?: number, title?: string, message?: string, kind: "begin" | "report" | "end"}]]
    if not client or type(value) ~= "table" then
      return
    end
    local p = progress[client.id]

    for i = 1, #p + 1 do
      if i == #p + 1 or p[i].token == ev.data.params.token then
        p[i] = {
          token = ev.data.params.token,
          msg = ("[%3d%%] %s%s"):format(
            value.kind == "end" and 100 or value.percentage or 100,
            value.title or "",
            value.message and (" **%s**"):format(value.message) or ""
          ),
          done = value.kind == "end",
        }
        break
      end
    end

    local msg = {} ---@type string[]
    progress[client.id] = vim.tbl_filter(function(v)
      return table.insert(msg, v.msg) or not v.done
    end, p)

    local spinner = { "", "", "", "", "", "", "", "", "", "" }
    vim.notify(table.concat(msg, "\n"), "info", {
      id = "lsp_progress",
      title = client.name,
      opts = function(notif)
        notif.icon = #progress[client.id] == 0 and ""
          or spinner[math.floor(vim.uv.hrtime() / (1e6 * 80)) % #spinner + 1]
      end,
    })
  end,
})

Expected Behavior

It should not flicker. The following hack fixes it, the key here being cursor = false being passed to nvim__redraw. Maybe there should be an option for the snacks.win to disable cursor redrawing?

---@type table<number, {token:lsp.ProgressToken, msg:string, done:boolean}[]>
local progress = vim.defaulttable()
vim.api.nvim_create_autocmd("LspProgress", {
  ---@param ev {data: {client_id: integer, params: lsp.ProgressParams}}
  callback = function(ev)
    local client = vim.lsp.get_client_by_id(ev.data.client_id)
    local value = ev.data.params.value --[[@as {percentage?: number, title?: string, message?: string, kind: "begin" | "report" | "end"}]]
    if not client or type(value) ~= "table" then
      return
    end
    local p = progress[client.id]

    for i = 1, #p + 1 do
      if i == #p + 1 or p[i].token == ev.data.params.token then
        p[i] = {
          token = ev.data.params.token,
          msg = ("[%3d%%] %s%s"):format(
            value.kind == "end" and 100 or value.percentage or 100,
            value.title or "",
            value.message and (" **%s**"):format(value.message) or ""
          ),
          done = value.kind == "end",
        }
        break
      end
    end

    local msg = {} ---@type string[]
    progress[client.id] = vim.tbl_filter(function(v)
      return table.insert(msg, v.msg) or not v.done
    end, p)

    local spinner = { "", "", "", "", "", "", "", "", "", "" }
    vim.notify(table.concat(msg, "\n"), "info", {
      id = "lsp_progress",
      title = client.name,
      opts = function(notif)
        if notif.win then
          notif.win.redraw = function(self)
            ---@diagnostic disable-next-line: need-check-nil
            vim.api.nvim__redraw({ win = self.win, valid = false, flush = true, cursor = false })
          end
        end

        notif.icon = #progress[client.id] == 0 and ""
          or spinner[math.floor(vim.uv.hrtime() / (1e6 * 80)) % #spinner + 1]
      end,
    })
  end,
})

Repro

vim.env.LAZY_STDPATH = ".repro"
load(vim.fn.system("curl -s https://raw.githubusercontent.com/folke/lazy.nvim/main/bootstrap.lua"))()
---@type table<number, {token:lsp.ProgressToken, msg:string, done:boolean}[]>
local progress = vim.defaulttable()
vim.api.nvim_create_autocmd("LspProgress", {
  ---@param ev {data: {client_id: integer, params: lsp.ProgressParams}}
  callback = function(ev)
    local client = vim.lsp.get_client_by_id(ev.data.client_id)
    local value = ev.data.params.value --[[@as {percentage?: number, title?: string, message?: string, kind: "begin" | "report" | "end"}]]
    if not client or type(value) ~= "table" then
      return
    end
    local p = progress[client.id]

    for i = 1, #p + 1 do
      if i == #p + 1 or p[i].token == ev.data.params.token then
        p[i] = {
          token = ev.data.params.token,
          msg = ("[%3d%%] %s%s"):format(
            value.kind == "end" and 100 or value.percentage or 100,
            value.title or "",
            value.message and (" **%s**"):format(value.message) or ""
          ),
          done = value.kind == "end",
        }
        break
      end
    end

    local msg = {} ---@type string[]
    progress[client.id] = vim.tbl_filter(function(v)
      return table.insert(msg, v.msg) or not v.done
    end, p)

    local spinner = { "", "", "", "", "", "", "", "", "", "" }
    vim.notify(table.concat(msg, "\n"), "info", {
      id = "lsp_progress",
      title = client.name,
      opts = function(notif)
        notif.icon = #progress[client.id] == 0 and ""
          or spinner[math.floor(vim.uv.hrtime() / (1e6 * 80)) % #spinner + 1]
      end,
    })
  end,
})

require("lazy.minit").repro({
  spec = {
    { "folke/snacks.nvim", opts = { notifier = {} } },
    -- add any other plugins here
  },
})
@synic synic added the bug Something isn't working label Jan 18, 2025
@synic synic changed the title bug: notifier causes cursor flickering (especially with lsp progress) bug: notifier causes cursor flickering (especially with lsp progress - with possible solution) Jan 18, 2025
@synic
Copy link
Author

synic commented Jan 18, 2025

I can probably add the fix for it, however, I am unsure of what to call the snacks.win option to disable cursor redrawing. Maybe redraw_opts? with the booleans valid, flush and cursor?

@folke
Copy link
Owner

folke commented Jan 18, 2025

This sounds more like a Neovim bug than anything else.
Have you tried on nightly?

@folke folke closed this as completed in ad9b382 Jan 18, 2025
@folke
Copy link
Owner

folke commented Jan 18, 2025

I just added cursor=false. Not really needed anyway.

@synic
Copy link
Author

synic commented Jan 18, 2025

Unfortunately, I was wrong, this actually did not fix the issue. I tried it on neovim nightly as well, same situation. You can easily reproduce it by getting the lsp progress notification on the screen, and then start a search with /.

Looks like this:

Image

@synic
Copy link
Author

synic commented Jan 18, 2025

Just a note, if you start a search, but don't hit enter, the notification stays on the screenforever, and the flickering will always be there

synic added a commit to synic/snacks.nvim that referenced this issue Jan 19, 2025
The constant calls to `schedule`, even when the UI is blocked due to the
current mode was causing the flickering I was seeing.
folke added a commit that referenced this issue Jan 19, 2025
folke pushed a commit that referenced this issue Jan 19, 2025
🤖 I have created a release *beep* *boop*
---


##
[2.13.0](v2.12.0...v2.13.0)
(2025-01-19)


### Features

* **picker.actions:** added support for action options. Fixes
[#598](#598)
([8035398](8035398))
* **picker.buffers:** del buffer with ctrl+x
([2479ff7](2479ff7))
* **picker.buffers:** delete buffers with dd
([2ab18a0](2ab18a0))
* **picker.commands:** added builtin commands. Fixes
[#634](#634)
([ee988fa](ee988fa))
* **picker.frecency:** cleanup old entries from sqlite3 database
([320a4a6](320a4a6))
* **picker.git:** added `git_diff` picker for diff hunks
([#519](#519))
([cc69043](cc69043))
* **picker.git:** git diff/show can now use native or neovim for
preview. defaults to neovim. Closes
[#500](#500). Closes
[#494](#494). Closes
[#491](#491). Closes
[#478](#478)
([e36e6af](e36e6af))
* **picker.git:** stage/unstage files in git status with `&lt;tab&gt;`
key
([0892db4](0892db4))
* **picker.grep:** added `ft` (rg's `type`) and `regex` (rg's
`--fixed-strings`) options
([0437cfd](0437cfd))
* **picker.list:** added debug option to show scores
([821e231](821e231))
* **picker.list:** added select_all action mapped to ctrl+a
([c9e2695](c9e2695))
* **picker.list:** better way of highlighting field patterns
([924a988](924a988))
* **picker.list:** make `conceallevel` configurable. Fixes
[#635](#635)
([d88eab6](d88eab6))
* **picker.lsp:** added `lsp_workspace_symbols`. Supports live search.
Closes [#473](#473)
([348307a](348307a))
* **picker.matcher:** added opts.matcher.sort_empty and
opts.matcher.filename_bonus
([ed91078](ed91078))
* **picker.matcher:** better scoring algorithm based on fzf. Closes
[#512](#512). Fixes
[#513](#513)
([e4e2e88](e4e2e88))
* **picker.matcher:** integrate custom item scores
([7267e24](7267e24))
* **picker.matcher:** moved length tiebreak to sorter instead
([d5ccb30](d5ccb30))
* **picker.recent:** include open files in recent files. Closes
[#487](#487)
([96ffaba](96ffaba))
* **picker.score:** prioritize matches in filenames
([5cf5ec1](5cf5ec1))
* **picker.smart:** better frecency bonus
([74feefc](74feefc))
* **picker.sort:** default sorter can now sort by len of a field
([6ae87d9](6ae87d9))
* **picker.sources:** lines just sorts by score/idx. Smart sorts on
empty
([be42182](be42182))
* **picker:** add qflist_all action to send all even when already sel…
([#600](#600))
([c7354d8](c7354d8))
* **picker:** add some source aliases like the Telescope / FzfLua names
([5a83a8e](5a83a8e))
* **picker:** added `{preview}` and `{flags}` title placeholders. Closes
[#557](#557), Closes
[#540](#540)
([2e70b7f](2e70b7f))
* **picker:** added `git_branches` picker. Closes
[#614](#614)
([8563dfc](8563dfc))
* **picker:** added `inspect` action mapped to `&lt;c-i&gt;`. Useful to
see what search fields are available on an item.
([2ba165b](2ba165b))
* **picker:** added `smart` picker
([772f3e9](772f3e9))
* **picker:** added exclude option for files and grep. Closes
[#581](#581)
([192fb31](192fb31))
* **picker:** added jump options jumplist(true for all), reuse_win &
tagstack (true for lsp). Closes
[#589](#589). Closes
[#568](#568)
([84c3738](84c3738))
* **picker:** added preliminary support for combining finder results.
More info coming soon
([000db17](000db17))
* **picker:** added spelling picker. Closes
[#625](#625)
([b170ced](b170ced))
* **picker:** added support for live args for `grep` and `files`. Closes
[#601](#601)
([50f3c3e](50f3c3e))
* **picker:** added toggle/flag/action for `follow`. Closes
[#633](#633)
([aa53f6c](aa53f6c))
* **picker:** allow disabling file icons
([76fbf9e](76fbf9e))
* **picker:** allow setting a custom `opts.title`. Fixes
[#620](#620)
([6001fb2](6001fb2))
* **picker:** custom icon for unselected entries
([#588](#588))
([6402687](6402687))
* **picker:** restore cursor / topline on resume
([ca54948](ca54948))
* **pickers.format:** added `opts.picker.formatters.file.filename_first`
([98562ae](98562ae))
* **picker:** use an sqlite3 database for frecency data when available
([c43969d](c43969d))
* **scroll:** faster animations for scroll repeats after delay.
(replaces spamming handling)
([d494a9e](d494a9e))
* **snacks:** added `snacks.picker`
([#445](#445))
([559d6c6](559d6c6))
* **toggle:** allow toggling global options. Fixes
[#534](#534)
([b50effc](b50effc))
* **win:** warn on duplicate keymaps that differ in case. See
[#554](#554)
([a71b7c0](a71b7c0))


### Bug Fixes

* **animate:** never animate stopped animations
([197b0a9](197b0a9))
* **bigfile:** check existence of NoMatchParen before executing
([#561](#561))
([9b8f57b](9b8f57b))
* **config:** better vim.tbl_deep_extend that prevents issues with
list-like tables. Fixes
[#554](#554)
([75eb16f](75eb16f))
* **config:** dont exclude metatables
([2d4a0b5](2d4a0b5))
* **grep:** explicitely set `--no-hidden` because of the git filter
([ae2de9a](ae2de9a))
* **indent:** dont redraw when list/shiftwidth/listchars change.
Triggered way too often. Fixes
[#613](#613). Closes
[#627](#627)
([d212e3c](d212e3c))
* **input:** bring back `&lt;c-w&gt;`. Fixes
[#426](#426). Closes
[#429](#429)
([5affa72](5affa72))
* **layout:** allow root with relative=cursor. Closes
[#479](#479)
([f06f14c](f06f14c))
* **layout:** don't trigger events during re-layout on resize. Fixes
[#552](#552)
([d73a4a6](d73a4a6))
* **layout:** open/update windows in order of the layout to make sure
offsets are correct
([034d50d](034d50d))
* **layout:** use eventignore when updating windows that are already
visible to fix issues with synatx. Fixes
[#552](#552)
([f7d967c](f7d967c))
* **lsp:** use treesitter highlights for LSP locations
([fc06a36](fc06a36))
* **notifier:** content width. Fixes
[#631](#631)
([0e27737](0e27737))
* **picker.actions:** added hack to make folds work. Fixes
[#514](#514)
([df1060f](df1060f))
* **picker.actions:** close existing empty buffer if it's the current
buffer
([0745505](0745505))
* **picker.actions:** full path for qflist and loclist actions
([3e39250](3e39250))
* **picker.actions:** only delete empty buffer if it's not displayed in
a window. Fixes [#566](#566)
([b7ab888](b7ab888))
* **picker.actions:** return action result. Fixes
[#612](https://github.com/folke/snacks.nvim/issues/612). See
[#611](#611)
([4a482be](4a482be))
* **picker.colorscheme:** nil check. Fixes
[#575](#575)
([de01907](de01907))
* **picker.config:** allow merging list-like layouts with table layout
options
([706b1ab](706b1ab))
* **picker.config:** better config merging and tests
([9986b47](9986b47))
* **picker.config:** normalize keys before merging so you can override
`&lt;c-s&gt;` with `<C-S>`
([afef949](afef949))
* **picker.db:** remove tests
([71f69e5](71f69e5))
* **picker.diagnostics:** sort on empty pattern. Fixes
[#641](#641)
([c6c76a6](c6c76a6))
* **picker.files:** ignore errors since it's not possible to know if the
error isbecause of an incomplete pattern. Fixes
[#551](#551)
([4823f2d](4823f2d))
* **picker.format:** filename
([a194bbc](a194bbc))
* **picker.format:** use forward slashes for paths. Closes
[#624](#624)
([5f82ffd](5f82ffd))
* **picker.git:** git log file/line for a file not in cwd. Fixes
[#616](#616)
([9034319](9034319))
* **picker.git:** git_file and git_line should only show diffs including
the file. Fixes [#522](#522)
([1481a90](1481a90))
* **picker.git:** use Snacks.git.get_root instead vim.fs.root for
backward compatibility
([a2fb70e](a2fb70e))
* **picker.highlight:** properly deal with multiline treesitter captures
([27b72ec](27b72ec))
* **picker.input:** don't set prompt interrupt, but use a `&lt;c-c&gt;`
mapping instead that can be changed
([123f0d9](123f0d9))
* **picker.input:** leave insert mode when closing and before executing
confirm. Fixes [#543](#543)
([05eb22c](05eb22c))
* **picker.input:** statuscolumn on resize / re-layout. Fixes
[#643](#643)
([4d8d844](4d8d844))
* **picker.input:** strip newllines from pattern (mainly due to pasting
in the input box)
([c6a9955](c6a9955))
* **picker.input:** use `Snacks.util.wo` instead of `vim.wo`. Fixes
[#643](#643)
([d6284d5](d6284d5))
* **picker.list:** disable folds
([5582a84](5582a84))
* **picker.list:** include `search` filter for highlighting items (live
search). See [#474](https://github.com/folke/snacks.nvim/issues/474)
([1693fbb](1693fbb))
* **picker.list:** newlines in text. Fixes
[#607](#607). Closes
[#580](#580)
([c45a940](c45a940))
* **picker.list:** possible issue with window options being set in the
wrong window
([f1b6c55](f1b6c55))
* **picker.list:** scores debug
([9499b94](9499b94))
* **picker.lsp:** added support for single location result
([79d27f1](79d27f1))
* **picker.matcher:** initialize matcher with pattern from opts. Fixes
[#596](#596)
([c434eb8](c434eb8))
* **picker.matcher:** inverse scores
([1816931](1816931))
* **picker.minheap:** clear sorted on minheap clear. Fixes
[#492](#492)
([79bea58](79bea58))
* **picker.preview:** don't show line numbers for preview commands
([a652214](a652214))
* **picker.preview:** pattern to detect binary files was incorrect
([bbd1a08](bbd1a08))
* **picker.preview:** scratch buffer filetype. Fixes
[#595](#595)
([ece76b3](ece76b3))
* **picker.proc:** correct offset for carriage returns. Fixes
[#599](#599)
([a01e0f5](a01e0f5))
* **picker.qf:** better quickfix item list. Fixes
[#562](#562)
([cb84540](cb84540))
* **picker.select:** allow main to be current. Fixes
[#497](#497)
([076259d](076259d))
* **picker.util:** cleanup func for key-value store (frecency)
([bd2da45](bd2da45))
* **picker:** add alias for `oldfiles`
([46554a6](46554a6))
* **picker:** add keymaps for preview scratch buffers
([dc3f114](dc3f114))
* **picker:** always stopinsert, even when picker is already closed.
Should not be needed, but some plugins misbehave. See
[#579](#579)
([29becb0](29becb0))
* **picker:** better buffer edit. Fixes
[#593](#593)
([716492c](716492c))
* **picker:** better normkey. Fixes
[#610](#610)
([540ecbd](540ecbd))
* **picker:** changed inspect mapping to `&lt;a-d&gt;` since not all
terminal differentiate between `<a-i>` and `<tab>`
([8386540](8386540))
* **picker:** correctly normalize path after fnamemodify
([f351dcf](f351dcf))
* **picker:** deepcopy before config merging. Fixes
[#554](#554)
([7865df0](7865df0))
* **picker:** don't throttle preview if it's the first item we're
previewing
([b785167](b785167))
* **picker:** dont fast path matcher when finder items have scores
([2ba5602](2ba5602))
* **picker:** format: one too many spaces for default icon in results …
([#594](#594))
([d7f727f](d7f727f))
* **picker:** picker:items() should return filtered items, not finder
items. Closes [#481](#481)
([d67e093](d67e093))
* **picker:** potential issue with preview winhl being set on the main
window
([34208eb](34208eb))
* **picker:** preview / lsp / diagnostics positions were wrong; Should
all be (1-0) indexed. Fixes
[#543](#543)
([40d08bd](40d08bd))
* **picker:** properly handle `opts.layout` being a string. Fixes
[#636](#636)
([b80c9d2](b80c9d2))
* **picker:** select_and_prev should use list_up instead of list_down
([#471](#471))
([b993be7](b993be7))
* **picker:** set correct cwd for git status picker
([#505](#505))
([2cc7cf4](2cc7cf4))
* **picker:** show all files in git status
([#586](#586))
([43c312d](43c312d))
* **scope:** make sure to parse the ts tree. Fixes
[#521](#521)
([4c55f1c](4c55f1c))
* **scratch:** autowrite right buffer. Fixes
[#449](#449).
([#452](#452))
([8d2e26c](8d2e26c))
* **scroll:** don't animate for new changedtick. Fixes
[#384](#384)
([ac8b3cd](ac8b3cd))
* **scroll:** don't animate when recording or executing macros
([7dcdcb0](7dcdcb0))
* **statuscolumn:** return "" when no signs and no numbers are needed.
Closes [#570](#570).
([c4980ef](c4980ef))
* **util:** normkey
([cd58a14](cd58a14))
* **win:** clear syntax when setting filetype
([c49f38c](c49f38c))
* **win:** correctly deal with initial text containing newlines. Fixes
[#542](#542)
([825c106](825c106))
* **win:** duplicate keymap should take mode into account. Closes
[#559](#559)
([097e68f](097e68f))
* **win:** exclude cursor from redraw. Fixes
[#613](#613)
([ad9b382](ad9b382))
* **win:** fix relative=cursor again
([5b1cd46](5b1cd46))
* **win:** relative=cursor. Closes
[#427](#427). Closes
[#477](#477)
([743f8b3](743f8b3))
* **win:** special handling of `&lt;C-J&gt;`. Closes
[#565](#565). Closes
[#592](#592)
([5ac80f0](5ac80f0))
* **win:** win position with border offsets. Closes
[#413](#413). Fixes
[#423](#423)
([ee08b1f](ee08b1f))
* **words:** added support for new name of the namespace used for lsp
references. Fixes
[#555](#555)
([566f302](566f302))


### Performance Improvements

* **notifier:** skip processing during search. See
[#627](#627)
([cf5f56a](cf5f56a))
* **picker.matcher:** fast path when we already found a perfect match
([6bbf50c](6bbf50c))
* **picker.matcher:** only use filename_bonus for items that have a file
field
([fd854ab](fd854ab))
* **picker.matcher:** yield every 1ms to prevent ui locking in large
repos
([19979c8](19979c8))
* **picker.util:** cache path calculation
([7117356](7117356))
* **picker:** dont use prompt buffer callbacks
([8293add](8293add))
* **picker:** matcher optims
([5295741](5295741))

---
This PR was generated with [Release
Please](https://github.com/googleapis/release-please). See
[documentation](https://github.com/googleapis/release-please#release-please).

Co-authored-by: github-actions[bot] <41898282+github-actions[bot]@users.noreply.github.com>
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
bug Something isn't working
Projects
None yet
2 participants