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

Heirline crashes when there isn't enough horizontal space for the bar #79

Closed
alexandersix opened this issue Oct 21, 2022 · 27 comments
Closed

Comments

@alexandersix
Copy link

Overview

Hi there! Love Heirline–it's probably the best Nvim statusline I've used.

I'm having an odd error that's causing some problems in my normal workflow. I use AstroNvim which, as of the 2.0 release, comes with Heirline preinstalled and preconfigured (configuration below). Recently, I started getting seemingly random crashes and stack overflows (screenshot also below) from Heirline, and when I dug into it today, I realized that Heirline only encounters the error when there is not enough horizontal space to fit all of the statusline information.

I went through the stack trace and realized that the error originates from the expand_or_contract_flexible_components function in the `utils.lua file, which seems to be in line with my observations about window width. From there, though, I'm not well-versed enough in Lua to really have much more of an insight into what could be happening.

Hopefully, this can point someone in the right direction to opening a PR fix! I'm happy to help however I can, so don't hold back with questions or other things to try!

Steps to Reproduce

  1. Expand terminal emulator to the full width of screen (obviously, results here will vary based on screen size and resolution)
  2. Open a file in Nvim and see the statusbar work
  3. Shrink terminal emulator window to the point where parts of the statusbar have collapsed
  4. Perform an action like creating a new split to the right (multiple actions can do this, but this is the simplest for example's sake)
  5. See the error appear
  6. Heirline is replaced with the default Nvim statusline

Details

Neovim Version: 0.8 - Release

Last Updated:

  • AstroNvim - today (10/21/22)
  • Heirline - today (10/21/22)
Heirline Configuration
heirline = function(config)
      -- statusline
      config[1] = {
        hl = { fg = "fg", bg = "bg" },
        astronvim.status.component.mode(),
        astronvim.status.component.git_branch(),
        astronvim.status.component.file_info(
          astronvim.is_available "bufferline.nvim" and { filetype = {}, filename = false, file_modified = false } or nil
        ),
        astronvim.status.component.git_diff(),
        astronvim.status.component.diagnostics(),
        astronvim.status.component.fill(),
        astronvim.status.component.macro_recording(),
        astronvim.status.component.fill(),
        astronvim.status.component.lsp(),
        astronvim.status.component.treesitter(),
        astronvim.status.component.nav(),
        astronvim.status.component.mode { surround = { separator = "right" } },
      }

      -- winbar
      config[2] = {
        fallthrough = false,
        -- if the current buffer matches the following buftype or filetype, disable the winbar
        {
          condition = function()
            return astronvim.status.condition.buffer_matches {
              buftype = { "terminal", "prompt", "nofile", "help", "quickfix" },
              filetype = { "NvimTree", "neo-tree", "dashboard", "Outline", "aerial" },
            }
          end,
          init = function() vim.opt_local.winbar = nil end,
        },
        -- if the window is currently active, show the breadcrumbs
        {
          condition = astronvim.status.condition.is_active,
          astronvim.status.component.breadcrumbs { hl = { fg = "winbar_fg", bg = "winbar_bg" } },
        },
        -- if the window is not currently active, show the file information
        {
          astronvim.status.component.file_info {
            file_icon = { highlight = false },
            hl = { fg = "winbarnc_fg", bg = "winbarnc_bg" },
            surround = false,
          },
        },
      }

      -- return the final configuration table
      return config
    end,
Screenshots:

Heirline (for reference)
image

Stack Overflow Error
image

@rebelot
Copy link
Owner

rebelot commented Oct 22, 2022

Dear @alexandersix, thanks for your efforts!

Unfortunately I am unable to reproduce the issue with my and other testing configurations.

This is likely fault of one flexible component, and possibly one that generates children each time it is evaluated (possibly a navigation component), which then creates an infinite call stack every time it's evaluated to check its lenght.

I think you should verify that this is the case, then I'll have a look at AstroNvim implementation of components

@alexandersix
Copy link
Author

Hey @rebelot, thanks for taking a look! Sorry that you weren't able to reproduce–definitely sounds like an issue with a specific AstroNvim flexible component.

I'll take a look and figure out which component specifically is causing the issue. Thanks for the insight about a potential issue being a component that generates children on each evaluation; I'll start my search there!

I'll report back when I have more information. It's it's an AstroNvim-specific component, I'll open an issue on that repository as well.

@Uduchi2nd
Copy link

Uduchi2nd commented Oct 25, 2022

Hi,
I also ran it this problem with Astrovim. For my situation, it was much easier to reproduce than @alexandersix 's . I just had to spin up more than 7-8 vsplits, and heirline will crash.
I tested each AstroVim component individually, and found the astronvim.status.component.lsp() to be the culprit. When it is the only component enabled, creating a lot of vsplit will cause heirline to crash.

@mehalter
Copy link
Contributor

@rebelot Just a heads up, the component that was originally reported is just using vim.fn.fnamemodify(vim.api.nvim_bug_get_name(0), "%p") as the function provider. This component doesn't use the flexible component stuff at all directly so I'm confused why Heirline's utils are jumping in here. Our (AstroNvim's) status API doesn't actually use heirline.utils at all so I figured it would separate us from that code. It seems to pop up when the full path gets very very long and we aren't trimming it down. This might be what's causing the stack overflow? Could you shine some insight why the expand_or_contract_flexible_component stuff is kicking in when we aren't using it.

@rebelot
Copy link
Owner

rebelot commented Oct 26, 2022

@mehalter

Because of this in the main eval function:

utils.expand_or_contract_flexible_components(statusline._flexible_components, full_width, out)

the utility is always called, but should return immediately if there's no flexible components:

if not flexible_components or not next(flexible_components) then
return
end

But from the stack trace, it seems there are indeed flexible components to evaluate (eval is being called from there, this is needed to check the length of the component)

@rebelot
Copy link
Owner

rebelot commented Oct 26, 2022

I really wouldn't know how to reproduce this and I have a pretty complex config..

Screenshot 2022-10-26 at 18 17 35

@rebelot
Copy link
Owner

rebelot commented Oct 26, 2022

could you please try b6044c8? I still can't really figure out why that's happening but following the stack trace that's where __index gets called seemingly endlessly.

@mehalter
Copy link
Contributor

Ah, I'm not completely understanding of what this is doing under the hood, but I do think that this resolves the problem for me! :D Thanks so much for taking a look @rebelot ! I will keep testing it and see if I can get another stack overflow, but I do think this resolves it

@mehalter
Copy link
Contributor

@alexandersix @Uduchi2nd could you guys also test this latest commit and see if it resolves your stack overflows? :)

@alexandersix
Copy link
Author

alexandersix commented Oct 27, 2022

@mehalter I'll give it a go as soon as possible and report back.

Thank you and @rebelot for being so quick about working on this. Massive props to the both of you 🙌🏻

--EDIT--
I pulled that commit using Packer and am sadly still having the same overflow issue. Here's the new trace (it just has new line numbers now, associated with the changes that were made in the recent commits)

New Stack Trace image

@vycoder
Copy link

vycoder commented Oct 29, 2022

I'm encountering the same issue. In my setup, it happens on a smaller monitor with a lower resolution (1366 x 768). Then I have a v-split. Save the session, quit then re-open the session.

Note that the error won't show the first time you do a v-split. It errors out when it starts to load on a vsplit with minimal on low resolution screen.

@krishnakumarg1984
Copy link

I still have this same issue when opening even a single vertical split on my 14" laptop. I am on AstroNvim too.

@rebelot
Copy link
Owner

rebelot commented Oct 29, 2022

Can you please retry with latest commit?

I am very sorry but being unable to reproduce the issues I am going a little bit blind here. I would be super helpful if some of you could pinpoint the component that's causing the error so I can take a look at the implementation.

@mehalter does AstroNvim make use of flexible components anywhere? I am very surprised to see the error spawns from a function that should run to that point only if flexible components are present

could some of you try this command and report the output?

:lua for _, c in ipairs(require'heirline'.statusline._flexible_components) do vim.pretty_print(c.id) end

@alexandersix
Copy link
Author

alexandersix commented Oct 29, 2022

Retry w/ Latest Commit

Hey @rebelot, just tried with your latest commit (9b814e6) and I'm still getting the same error.

Pinpointing the Erroring Component

Also, I'm so sorry, I misread an earlier comment and thought we had narrowed down which component was causing the issues!

I took some time this morning to play around with the default Heirline config in AstroNvim by adding the snippet found here (second image in this section) to my user configuration file. Turns out, at least from what I can tell, the lsp() component is causing the issue, but only when it is paired with another component on the bar.

So, for example, with a configuration that looks like this:

["heirline"] = function(config)
    config[1] = {
        hl = { fg = "fg", bg = "bg" },
        astronvim.status.component.lsp(),
    }
end

Heirline works no matter the size of the window.

However, with a configuration that looks like this:

["heirline"] = function(config)
    config[1] = {
        hl = { fg = "fg", bg = "bg" },
        astronvim.status.component.lsp(),
        astronvim.status.component.treesitter(),
    }
end

Heirline will throw that error when the screen is too narrow to fit both components.

To reiterate, this issue with the lsp() component occurs with any of the other components, not just treesitter(), provided you can get the nvim window narrow enough.

Command Output

Also, I tried the command you put in your last comment and this was the result:

{ 9, 2, 1, 1, 1 }

@krishnakumarg1984
Copy link

could some of you try this command and report the output?

{ 6, 1, 1, 1, 1 }
{ 9, 2, 1, 1, 1 }

@krishnakumarg1984
Copy link

Also, things are breaking even with a single window (i.e. a single buffer/split).

E5108: Error executing lua ...k/packer/start/heirline.nvim/lua/heirline/statusline.lua:369: stack overflow
stack traceback:
	...k/packer/start/heirline.nvim/lua/heirline/statusline.lua:368: in function 'traverse'
	...k/packer/start/heirline.nvim/lua/heirline/statusline.lua:372: in function 'traverse'
	...k/packer/start/heirline.nvim/lua/heirline/statusline.lua:372: in function 'traverse'
	...k/packer/start/heirline.nvim/lua/heirline/statusline.lua:372: in function 'traverse'
	...k/packer/start/heirline.nvim/lua/heirline/statusline.lua:372: in function 'traverse'
	...k/packer/start/heirline.nvim/lua/heirline/statusline.lua:372: in function 'traverse'
	...k/packer/start/heirline.nvim/lua/heirline/statusline.lua:372: in function 'traverse'
	...k/packer/start/heirline.nvim/lua/heirline/statusline.lua:372: in function 'traverse'
	...k/packer/start/heirline.nvim/lua/heirline/statusline.lua:372: in function 'traverse'
	...k/packer/start/heirline.nvim/lua/heirline/statusline.lua:372: in function 'traverse'
	...k/packer/start/heirline.nvim/lua/heirline/statusline.lua:372: in function 'traverse'
	...
	...k/packer/start/heirline.nvim/lua/heirline/statusline.lua:372: in function 'traverse'
	...k/packer/start/heirline.nvim/lua/heirline/statusline.lua:372: in function 'traverse'
	...k/packer/start/heirline.nvim/lua/heirline/statusline.lua:372: in function 'traverse'
	...k/packer/start/heirline.nvim/lua/heirline/statusline.lua:372: in function 'traverse'
	...k/packer/start/heirline.nvim/lua/heirline/statusline.lua:372: in function 'traverse'
	...k/packer/start/heirline.nvim/lua/heirline/statusline.lua:372: in function 'traverse'
	...k/packer/start/heirline.nvim/lua/heirline/statusline.lua:372: in function 'traverse'
	...k/packer/start/heirline.nvim/lua/heirline/statusline.lua:382: in function 'eval'
	...e/pack/packer/start/heirline.nvim/lua/heirline/utils.lua:229: in function 'expand_or_contract_flexible_components'
	...te/pack/packer/start/heirline.nvim/lua/heirline/init.lua:75: in function <...te/pack/packer/start/heirline.nvim/lua/heirline/init.lua:66>

@krishnakumarg1984
Copy link

krishnakumarg1984 commented Oct 29, 2022

I also get a (different?) error when, after opening an empty buffer, I open a lua file: E5108: Error executing lua error in error handling and then the status line disappears.

@alexandersix
Copy link
Author

I also get a (different?) error when, after opening an empty buffer, I open a lua file: E5108: Error executing lua error in error handling and then the status line disappears.

@krishnakumarg1984 I've been noticing that I get this error when I open a window too quickly after launching Nvim. Possibly related, but I'd imagine that something involving the error handling hasn't loaded so instead of showing the full stack trace, this little error is shown. I don't have the Lua chops to prove that, but that's what I'd assume.

@mehalter
Copy link
Contributor

@rebelot we do make use of the flexibile components in the lsp component where we display the LSP client names and if it gets too long then it will shorten them.

@rebelot
Copy link
Owner

rebelot commented Oct 29, 2022

@alexandersix Thank you again for your clarifications. I was confused by a comment stating the pinpointed component was different from what you originally reported.

I am looking at the layers cast by AstroNvim over heirline. I might just end up installing it to help with debugging. I am going to ask you one last curtesy, could you please use this config

["heirline"] = function(config)
    config[1] = {
        hl = { fg = "fg", bg = "bg" },
        astronvim.status.component.lsp(),
        astronvim.status.component.treesitter(),
    }
end

and report the output of the command

:lua =require'heirline'.statusline

@rebelot
Copy link
Owner

rebelot commented Oct 29, 2022

I could finally find a way to reproduce. The bug is due to setting the update field directly to a flexible component. I still need to grasp why.

local crash = u.make_flexible_component(1, { provider = "craaaaaaaaaaaaaaaaaaash" }, { provider = "or not" })
crash.update = { "User", pattern = "DoCrash" }

require("heirline").setup(crash)
vim.cmd("wincmd 300v | doau User DoCrash")

This won't crash

local dontcrash = {
    update = { "User", pattern = "DoCrash" },
    u.make_flexible_component(1, { provider = "craaaaaaaaaaaaaaaaaaash" }, { provider = "or not" })
}
require("heirline").setup(dontcrash)
vim.cmd("wincmd 300v | doau User DoCrash")

Once I understand why this is happening we'll be able to fix this

@alexandersix
Copy link
Author

That's fantastic @rebelot! Glad we could finally reproduce it–makes me feel a little less crazy.

In case you still need it, here's the output from =require'heirline'.statusline. If there's any other way I can help, let me know!

Command Output
<1>{ <2>{ {
      _tree = <3>{ "%#Stl2c323c_2c323c__#  %*" },
      fallthrough = true,
      hl = <function 1>,
      id = { 1, 1 },
      merged_hl = {
        bg = "bg",
        fg = "lsp_bg"
      },
      provider = "  ",
      <metatable> = <table 2>
    }, <4>{ <5>{ <6>{ <7>{ {
              _tree = <8>{ "%#Stlcdd6f4_2c323c__#%*" },
              fallthrough = true,
              id = { 1, 2, 1, 1, 1, 1 },
              merged_hl = {
                bg = "lsp_bg",
                fg = "lsp_fg"
              },
              provider = <function 2>,
              <metatable> = <table 7>
            }, {
              fallthrough = true,
              id = { 1, 2, 1, 1, 1, 2 },
              provider = "",
              <metatable> = <table 7>
            },
            __index = <function 3>,
            _priority = 1,
            _tree = <9>{ <table 8> },
            _win_child_index = { 1 },
            fallthrough = true,
            id = { 1, 2, 1, 1, 1 },
            init = <function 4>,
            merged_hl = {
              bg = "lsp_bg",
              fg = "lsp_fg"
            },
            pick_child = { 1 },
            restrict = {
              _win_child_index = true
            },
            <metatable> = <table 6>
         }, <10>{ {
              _tree = { "%#Stlcdd6f4_2c323c__#  sumneko_lua, misspell, stylua, luacheck%*" },
              fallthrough = true,
              id = { 1, 2, 1, 1, 2, 1 },
              merged_hl = {
                bg = "lsp_bg",
                fg = "lsp_fg"
              },
              provider = <function 5>,
              <metatable> = <table 10>
            }, {
              fallthrough = true,
              id = { 1, 2, 1, 1, 2, 2 },
              provider = "  LSP",
              <metatable> = <table 10>
            },
            __index = <function 6>,
            _au_id = 97,
            _priority = 2,
            _tree = <11>{ "%#Stlcdd6f4_2c323c__#  sumneko_lua, misspell, stylua, luacheck%*" },
            _win_cache = { "%#Stlcdd6f4_2c323c__#  sumneko_lua, misspell, stylua, luacheck%*" },
            _win_child_index = { 1 },
            fallthrough = true,
            id = { 1, 2, 1, 1, 2 },
            init = <function 7>,
            merged_hl = {
              bg = "lsp_bg",
              fg = "lsp_fg"
            },
            pick_child = { 1 },
            restrict = {
              _win_child_index = true
            },
            update = { "LspAttach", "LspDetach", "BufEnter" },
            <metatable> = <table 6>
          },
          __index = <function 8>,
          _tree = <12>{ <table 9>, <table 11> },
          fallthrough = true,
          id = { 1, 2, 1, 1 },
          merged_hl = {
            bg = "lsp_bg",
            fg = "lsp_fg"
          },
        <metatable> = <table 5>
        },
        __index = <function 9>,
        _tree = <13>{ "%@v:lua.heirline_lsp@", <table 12>, "%X" },
        fallthrough = true,
        hl = {
          fg = "lsp_fg"
        },
        id = { 1, 2, 1 },
        merged_hl = {
          bg = "lsp_bg",
          fg = "lsp_fg"
        },
        on_click = {
          callback = <function 10>,
          name = "heirline_lsp"
        },
        <metatable> = <table 4>
      },
      __index = <function 11>,
      _tree = <14>{ <table 13> },
      fallthrough = true,
      hl = <function 12>,
      id = { 1, 2 },
      merged_hl = {
        bg = "lsp_bg",
        fg = "fg"
      },
      <metatable> = <table 2>
    },
    __index = <function 13>,
    _tree = <15>{ <table 3>, <table 14> },
    condition = <function 14>,
    fallthrough = true,
    id = { 1 },
    merged_hl = {
      bg = "bg",
      fg = "fg"
    },
    <metatable> = <table 1>
  }, <16>{ {
      _tree = <17>{ "%#Stl2c323c_2c323c__#  %*" },
      fallthrough = true,
      hl = <function 15>,
     id = { 2, 1 },
      merged_hl = {
        bg = "bg",
        fg = "treesitter_bg"
      },
      provider = "  ",
      <metatable> = <table 16>
    }, <18>{ <19>{ {
          _tree = { "%#Stla6e3a1_2c323c__#綠TS%*" },
          fallthrough = true,
          id = { 2, 2, 1, 1 },
          merged_hl = {
            bg = "treesitter_bg",
            fg = "treesitter_fg"
          },
          provider = "綠TS",
          <metatable> = <table 19>
        },
        __index = <function 16>,
        _au_id = 87,
        _tree = <20>{ "%#Stla6e3a1_2c323c__#綠TS%*" },
        _win_cache = { "%#Stla6e3a1_2c323c__#綠TS%*" },
        fallthrough = true,
        hl = {
          fg = "treesitter_fg"
        },
        id = { 2, 2, 1 },
        init = <function 17>,
        merged_hl = {
          bg = "treesitter_bg",
          fg = "treesitter_fg"
        },
        once = true,
        update = { "OptionSet",
          pattern = "syntax"
        },
        <metatable> = <table 18>
      },
      __index = <function 18>,
      _tree = <21>{ <table 20> },
      fallthrough = true,
      hl = <function 19>,
      id = { 2, 2 },
      merged_hl = {
       bg = "treesitter_bg",
        fg = "fg"
      },
      <metatable> = <table 16>
    },
    __index = <function 20>,
    _tree = <22>{ <table 17>, <table 21> },
    condition = <function 21>,
    fallthrough = true,
    id = { 2 },
    merged_hl = {
      bg = "bg",
      fg = "fg"
    },
    <metatable> = <table 1>
  },
  __index = <function 22>,
  _buflist = {},
  _flexible_components = { <table 7> },
  _tree = { <table 15>, <table 22> },
  _updatable_components = {},
  fallthrough = true,
  hl = <23>{
    bg = "bg",
    fg = "fg"
  },
  id = {},
  merged_hl = <table 23>,
  winnr = 1,
  <metatable> = {
    __index = <function 23>,
    _eval = <function 24>,
    _freeze_cache = <function 25>,
    broadcast = <function 26>,
    clear_tree = <function 27>,
    eval = <function 28>,
    find = <function 29>,
    get = <function 30>,
    get_win_attr = <function 31>,
    is_empty = <function 32>,
    local_ = <function 33>,
    new = <function 34>,
    nonlocal = <function 35>,
    set_win_attr = <function 36>,
   traverse = <function 37>
  }
}

@mehalter
Copy link
Contributor

Wow thank you so so much for your help @rebelot !!! Let me know if there is anything you would like me to do to help with the investigation!

rebelot added a commit that referenced this issue Oct 30, 2022
Set _win_cache only once at the end of the main evaluation loop
(_freeze_cache).
@rebelot
Copy link
Owner

rebelot commented Oct 30, 2022

It should be fixed now.

@mehalter on a side note, consider the difference here

-- 1
local updatable_flex = utils.make_flexible_component(1, { ... }, { ... })
updatable_flex.update = { ... }

-- 2
local flex_updatable = utils.make_flexible_component(1, { ..., update = {...}}, { ..., update = {...}})

in the first case, the flexible component won't shrink nor expand until the update event is fired, and this is intended behaviour, in the second case, the flexible component will shrink or expand, but the displayed child will just use the cached value. I If I read correctly, you're implementing a version of the first approach, but I think the second approach is what you actually want with the LSP component.

@alexandersix
Copy link
Author

@rebelot That seems to have done the trick! Thanks so much for doing all the hard work–I very much appreciate it! 🎉

@mehalter
Copy link
Contributor

Thank you again @rebelot for investigating this so thoroughly and identifying/implementing a fix so quickly :) Also thanks for going the extra mile and taking a look at our implementations as well ! What you said makes total sense and I have gone in and improved our LSP component implementation. Cannot express my gratitude enough for all of this! 🥳

@rebelot
Copy link
Owner

rebelot commented Oct 31, 2022

Thank you guys for your feedback and helping me to improve this plugin :)

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

No branches or pull requests

6 participants