grapp-dev / nui-components.nvim

A feature-rich and highly customizable library for creating user interfaces in Neovim.
https://nui-components.grapp.dev
MIT License
303 stars 6 forks source link

Error: Buffer is not 'modifiable' #17

Closed b0o closed 5 months ago

b0o commented 5 months ago

Under certain circumstances, I'm getting the following error:

Error executing vim.schedule lua callback: nui.nvim/lua/nui/line/init.lua:72: Buffer is not 'modifiable'
stack traceback:
    [C]: in function 'nvim_buf_set_lines'
    nui.nvim/lua/nui/line/init.lua:72: in function 'render'
    ...vim/nui-components.nvim/lua/nui-components/paragraph.lua:174: in function 'func'
    ...nvim/nui-components.nvim/lua/nui-components/utils/fn.lua:117: in function 'ieach'
    ...vim/nui-components.nvim/lua/nui-components/paragraph.lua:155: in function 'modify_fn'
    ...ui-components.nvim/lua/nui-components/component/init.lua:400: in function <...ui-components.nvim/lua/nui-components/component/init.lua:399>
mobily commented 5 months ago

@b0o could you provide a repro, please? 🙏

b0o commented 5 months ago

The issue depends on the timing of when the signal value is updated. Specifically, it seems to happen if the signal is updated in the middle of a render?

local n = require 'nui-components'

local renderer = n.create_renderer {
  width = math.min(vim.o.columns, 64),
  height = math.min(vim.o.lines, 20),
}

local signal = n.create_signal {
  stdout = '',
  stderr = '',
}

renderer:render(
  --
  n.rows(
    n.paragraph {
      is_focusable = false,
      -- Side note: Although we are mapping over the stdout signal value, the function is called when
      -- either stdout or stderr changes.
      -- In fact, it seems we can map over any signal field, even if it doesn't exist, like signal.foobar:map(...)
      -- IMO, we should be able to just do signal:map() instead of needing to index a specific field
      lines = signal.stdout:map(function()
        local val = signal:get_value()
        return {
          n.line(n.text('Stdout: ', 'Keyword'), val.stdout),
          n.line(n.text('Stderr: ', 'Keyword'), val.stderr),
        }
      end),
    },
    n.gap(1),
    n.button {
      label = ' Run Command ',
      autofocus = true,
      on_press = function()
        vim.fn.jobstart('for i in {1..10}; do sleep 0.1; echo "stdout: $i"; echo "stderr: $i" >&2; done', {
          on_stdout = function(_, data, _)
            if data and data[1] and data[1] ~= '' then
              signal.stdout = data[1]
            end
          end,
          on_stderr = function(_, data, _)
            if data and data[1] and data[1] ~= '' then
              signal.stderr = data[1]
            end
          end,
        })
      end,
    }
  )
)

You might need to press the Run Command button a few times to see the error.