nvim-lualine / lualine.nvim

A blazing fast and easy to configure neovim statusline plugin written in pure lua.
MIT License
5.93k stars 462 forks source link

Bug: Infinite highlight group creation (causes nvim to crash) #722

Closed Davincible closed 2 years ago

Davincible commented 2 years ago

Lualine keeps generating new highlights, eventually hitting the 20k hard limit of Neovim causing it to crash.

I'm suspecting it could have something to do with setting the background color to an empty string, but not sure.

It's related to this function in the evil theme;

ins_left({
    -- mode component
    function()
        return ""
    end,
    color = function()
        -- auto change color according to neovims mode
        local mode_color = {
            n = colors.red,
            i = colors.green,
            v = colors.blue,
            ["�"] = colors.blue,
            V = colors.blue,
            c = colors.magenta,
            no = colors.red,
            s = colors.orange,
            S = colors.orange,
            ["�"] = colors.orange,
            ic = colors.yellow,
            R = colors.violet,
            Rv = colors.violet,
            cv = colors.red,
            ce = colors.red,
            r = colors.cyan,
            rm = colors.cyan,
            ["r?"] = colors.cyan,
            ["!"] = colors.red,
            t = colors.red,
        }
        return { fg = mode_color[vim.fn.mode()] }
    end,
    padding = { right = 1 },
})
lualine_x_14042_replace xxx cleared
lualine_x_14042_command xxx cleared
lualine_x_14042_terminal xxx cleared
lualine_x_14042_inactive xxx cleared
lualine_x_14046_normal xxx cleared
lualine_x_14046_insert xxx cleared
lualine_x_14046_visual xxx cleared
lualine_x_14046_replace xxx cleared
lualine_x_14046_command xxx cleared
lualine_x_14046_terminal xxx cleared
lualine_x_14046_inactive xxx cleared
lualine_c_14047_normal xxx cleared
lualine_c_14047_insert xxx cleared
lualine_c_14047_visual xxx cleared
lualine_c_14047_replace xxx cleared
lualine_c_14047_command xxx cleared
lualine_c_14047_terminal xxx cleared
lualine_c_14047_inactive xxx cleared
lualine_c_14048_normal xxx cleared
lualine_c_14048_insert xxx cleared
lualine_c_14048_visual xxx cleared
lualine_c_14048_replace xxx cleared
lualine_c_14048_command xxx cleared
lualine_c_14048_terminal xxx cleared
lualine_c_14048_inactive xxx cleared
lualine_c_14055_normal xxx cleared
lualine_c_14055_insert xxx cleared
lualine_c_14055_visual xxx cleared
lualine_c_14055_replace xxx cleared
lualine_c_14055_command xxx cleared
lualine_c_14055_terminal xxx cleared
lualine_c_14055_inactive xxx cleared
lualine_x_14068_normal xxx cleared

Self Checks

How to reproduce the problem

Expected behaviour

Actual behaviour

Minimal config to reproduce the issue

Config ```lua -- Eviline config for lualine -- Author: shadmansaleh -- Credit: glepnir local lualine = require("lualine") -- Color table for highlights -- stylua: ignore local colors = { bg = "#202328", fg = "#bbc2cf", yellow = "#ECBE7B", cyan = "#008080", darkblue = "#081633", green = "#98be65", orange = "#FF8800", violet = "#a9a1e1", magenta = "#c678dd", blue = "#51afef", red = "#ec5f67", transparent = "" } local conditions = { buffer_not_empty = function() return vim.fn.empty(vim.fn.expand("%:t")) ~= 1 end, hide_in_width = function() return vim.fn.winwidth(0) > 80 end, check_git_workspace = function() local filepath = vim.fn.expand("%:p:h") local gitdir = vim.fn.finddir(".git", filepath .. ";") return gitdir and #gitdir > 0 and #gitdir < #filepath end, } -- Config local config = { extensions = { "quickfix", "fugitive", "fzf", "man", "nvim-dap-ui", "toggleterm" }, options = { -- component_separators = {left = "  ", right = "  "}, -- section_separators = {left = "", right = ""}, -- Disable sections and component separators component_separators = "", section_separators = "", theme = { -- We are going to use lualine_c an lualine_x as left and -- right section. Both are highlighted by c theme . So we -- are just setting default looks o statusline normal = { c = { fg = colors.fg, bg = colors.transparent } }, inactive = { c = { fg = colors.fg, bg = colors.transparent } }, }, }, sections = { -- these are to remove the defaults lualine_a = {}, lualine_b = {}, lualine_y = {}, lualine_z = {}, -- These will be filled later lualine_c = {}, lualine_x = {}, }, inactive_sections = { -- these are to remove the defaults lualine_a = {}, lualine_b = {}, lualine_y = {}, lualine_z = {}, lualine_c = {}, lualine_x = {}, }, } -- Inserts a component in lualine_c at left section local function ins_left(component) table.insert(config.sections.lualine_c, component) end -- Inserts a component in lualine_x ot right section local function ins_right(component) table.insert(config.sections.lualine_x, component) end ins_left({ function() return "▊" end, color = { fg = colors.blue }, -- Sets highlighting of component padding = { left = 0, right = 1 }, -- We don't need space before this }) ins_left({ -- mode component function() return "" end, color = function() -- auto change color according to neovims mode local mode_color = { n = colors.red, i = colors.green, v = colors.blue, [""] = colors.blue, V = colors.blue, c = colors.magenta, no = colors.red, s = colors.orange, S = colors.orange, [""] = colors.orange, ic = colors.yellow, R = colors.violet, Rv = colors.violet, cv = colors.red, ce = colors.red, r = colors.cyan, rm = colors.cyan, ["r?"] = colors.cyan, ["!"] = colors.red, t = colors.red, } return { fg = mode_color[vim.fn.mode()] } end, padding = { right = 1 }, }) ins_left({ -- filesize component "filesize", cond = conditions.buffer_not_empty, }) ins_left({ "filename", cond = conditions.buffer_not_empty, color = { fg = colors.magenta, gui = "bold" }, }) ins_left({ "location" }) ins_left({ "progress", color = { fg = colors.fg, gui = "bold" } }) ins_left({ "diagnostics", sources = { "nvim_diagnostic" }, symbols = { error = " ", warn = " ", info = " " }, diagnostics_color = { color_error = { fg = colors.red }, color_warn = { fg = colors.yellow }, color_info = { fg = colors.cyan }, }, }) -- Insert mid section. You can make any number of sections in neovim :) -- for lualine it's any number greater then 2 ins_left({ function() return "%=" end, }) ins_left({ -- Lsp server name . function() local msg = "No Active Lsp" local buf_ft = vim.api.nvim_buf_get_option(0, "filetype") local clients = vim.lsp.get_active_clients() if next(clients) == nil then return msg end for _, client in ipairs(clients) do local filetypes = client.config.filetypes if filetypes and vim.fn.index(filetypes, buf_ft) ~= -1 then return client.name end end return msg end, icon = " LSP:", color = { fg = "#ffffff", gui = "bold" }, }) -- Add components to right sections ins_right({ "o:encoding", -- option component same as &encoding in viml fmt = string.upper, -- I'm not sure why it's upper case either ;) cond = conditions.hide_in_width, color = { fg = colors.green, gui = "bold" }, }) ins_right({ "fileformat", fmt = string.upper, icons_enabled = false, -- I think icons are cool but Eviline doesn't have them. sigh color = { fg = colors.green, gui = "bold" }, }) ins_right({ "branch", icon = "", color = { fg = colors.violet, gui = "bold" }, }) ins_right({ "diff", -- Is it me or the symbol for modified us really weird symbols = { added = " ", modified = "柳 ", removed = " " }, diff_color = { added = { fg = colors.green }, modified = { fg = colors.orange }, removed = { fg = colors.red }, }, cond = conditions.hide_in_width, }) ins_right({ function() return "▊" end, color = { fg = colors.blue }, padding = { left = 1 }, }) lualine.setup(config) ```

Aditional information

Davincible commented 2 years ago

This is the code responsible for the infinite groups: https://github.com/nvim-lualine/lualine.nvim/blob/5113cdb32f9d9588a2b56de6d1df6e33b06a554a/lua/lualine/highlight.lua#L307-L313

Normally it wouldn't get called so often, but after debugging I found out that my changing background/colorscheme causes the code above to be re-evaluated. I don't see why this happens, as I am only changing my theme.

I'm also not really sure why the the tags get incremented, as I'd make more sense to overwrite highlight groups to change colors

Lush refresh theme code:

-- Watch pywal cache file for color changes
local fwatch = require("fwatch")
fwatch.watch("/home/tyler/.cache/wal/colors", {
    on_event = function()
        -- Reapply colorscheme 200ms after file changed
        vim.defer_fn(function()
            package.loaded["themes.pywal"] = nil
            require("lush")(require("themes.pywal"))
            vim.cmd("colorscheme pywal")
        end, 200)
    end,
})
shadmansaleh commented 2 years ago

When you run colorscheme command entire lualine's setup process is rerun so components can adapt to possibly changed colorscheme & theme . How often is that fwatch getting triggered ? Performance wise it's not a very good idea to call it too often.

Even then the counter should reset everytime you change colorscheme . This shouldn't happen at all . https://github.com/nvim-lualine/lualine.nvim/blob/5113cdb32f9d9588a2b56de6d1df6e33b06a554a/lua/lualine/highlight.lua#L199

Davincible commented 2 years ago

I see. Fwatch gets triggered once every 5 min, don't experience any performance hits.

I had a look at it again, and you're right about the counter being reset. I found out the number is passed in as as the highlight_tag argument to that create_component_highlight_group function. I had trouble figuring out the rootcause of the problem as the stacktrace wasn't too helpful

image

shadmansaleh commented 2 years ago

Ooo I get it now . the components highlight groups are attached to components id https://github.com/nvim-lualine/lualine.nvim/blob/5113cdb32f9d9588a2b56de6d1df6e33b06a554a/lua/lualine/component.lua#L8 https://github.com/nvim-lualine/lualine.nvim/blob/5113cdb32f9d9588a2b56de6d1df6e33b06a554a/lua/lualine/component.lua#L27-L31

the 14042 number you're seeing is the components id. because that id is unique for each component their hl groups also become unique even if the components are identical.

Because the components also get recreated each time lualine config gets reloaded so they get a new id . This was done to avoid any kind of conflicts that may arise from the previous instence of the component also custom function components all gets created from same function component class even through they can be completely different based on the actual function they need to be namespaced so they don't conflict with each other . I didn't think any one would actually every call setup enough times in a neovim session to make such ids an issue.

TBH I'm still not sure how you're getting this error . Hitting 20k limit should still be impractical . even if lualine creating 100 hl group for each setup and you're calling setup every 5 minutes you should still need about 16 hours of uptime to hit that 20k limit. I'm surprised you've managed to reach 14k component id in a neovim session.

Also if fwatch is getting triggered every 5 minutes does that mean your changing your system theme every 5 minutes ? what's actually modifying the /home/tyler/.cache/wal/colors file ?

Davincible commented 2 years ago

This was done to avoid any kind of conflicts that may arise from the previous instence of the component also custom function components all gets created from same function component class even through they can be completely different based on the actual function they need to be namespaced so they don't conflict with each other

I'm probably not grasping the full meaning these consequences, but naively put, shouldn't any same components just get overwritten on every re-render? As the old classes will be abandoned anyway upon the rerender.

Also if fwatch is getting triggered every 5 minutes does that mean your changing your system theme every 5 minutes ?

Yes. I run wpg -m from wpgtk in a daemon that will change my background, and change the resulting colorscheme with it, so new colors get written to the .cache/wal/colors file which triggers my neovim and terminal to refresh colorschemes. It's quite neat

I've previously always used galaxyline and had no issues. Recently moved over to Lualine and started seeing my neovim crash after having it open for a few hours

I didn't think any one would actually every call setup enough times in a neovim session to make such ids an issue.

That's a dangerous assumption in an editor where people like to push the boundaries :D

shadmansaleh commented 2 years ago

I'm probably not grasping the full meaning these consequences

simply if you have 2 function components one that show time and other that show date highlight groups created by 1st component shouldn't affect highlights of 2nd component.

shouldn't any same components just get overwritten on every re-render? As the old classes will be abandoned anyway upon the rerender.

no we only create instances of compnent during initialization and keep reusing them in the same lualine session to reduce redrawtime.

I think we can reset the component id counter on lualine setup call to resolve this issue without much consequences.

try this patch and see if this fixes the issue

diff --git a/lua/lualine/component.lua b/lua/lualine/component.lua
index 0432832..4ae7a2d 100644
--- a/lua/lualine/component.lua
+++ b/lua/lualine/component.lua
@@ -6,6 +6,9 @@ local M = require('lualine.utils.class'):extend()

 -- Used to provide a unique id for each component
 local component_no = 1
+function M.reset_component_id()
+  component_no = 1
+end

 -- variable to store component output for manipulation
 M.status = ''
diff --git a/lua/lualine/utils/loader.lua b/lua/lualine/utils/loader.lua
index 6a2893c..f0403ad 100644
--- a/lua/lualine/utils/loader.lua
+++ b/lua/lualine/utils/loader.lua
@@ -202,6 +202,7 @@ end
 ---loads sections and extensions or entire user config
 ---@param config table user config
 local function load_all(config)
+  require'lualine.component'.reset_component_id()
   load_components(config)
   load_extensions(config)
 end
Davincible commented 2 years ago

That seems to have fixed it!