Code Monkey home page Code Monkey logo

nui-components.nvim's Introduction

nui-components.nvim · PRs Welcome GitHub license

nui-components.nvim

A powerful tool that aims to make UI development in Neovim more accessible, intuitive, and enjoyable.

NuiComponents is a library built on top of nui.nvim, which provides an extensive set of tools for creating user interfaces in Neovim using Lua. With NuiComponents, developers can easily build complex UIs using a simple and intuitive API, which supports various UI elements. Moreover, the library includes advanced features like state management and form validations.

Documentation

Full documentation can be found here.

Installation

To install NuiComponents, you should use your preferred plugin manager.

Lazy

{
  "grapp-dev/nui-components.nvim",
  dependencies = {
    "MunifTanjim/nui.nvim"
  }
}

Packer

use {
  "grapp-dev/nui-components.nvim",
  requires = {
    "MunifTanjim/nui.nvim"
  }
}

Discord

Join Discord to get involved with the community, ask questions, and share tips.

For plugin developers

Consider publishing your plugin to luarocks to simplify installation with compatible plugin managers like rocks.nvim or lazy.nvim extended with luarocks.nvim.

License

The MIT License.

See LICENSE

nui-components.nvim's People

Contributors

b0o avatar mobily avatar mrcjkb avatar willothy avatar

Stargazers

 avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar

Watchers

 avatar  avatar  avatar  avatar  avatar

nui-components.nvim's Issues

Feature: add `signal:map()` method

I often find myself wanting to use several different signal values in a single prop. I'm currently doing something like this:

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 {
  foo = false,
  bar = false,
  qux = false,
}

renderer:render(
  --
  n.rows(
    n.paragraph {
      is_focusable = false,

      -- This is the important part:
      lines = signal.foo:map(function()
        local val = signal:get_value()
        return {
          n.line(n.text('Foo: ', 'Keyword'), tostring(val.foo)),
          n.line(n.text('Bar: ', 'Keyword'), tostring(val.bar)),
          n.line(n.text('Qux: ', 'Keyword'), tostring(val.qux)),
        }
      end),

    },
    n.gap(1),
    n.columns(
      { flex = 0 },
      n.button {
        label = ' Toggle Foo ',
        autofocus = true,
        on_press = function()
          signal.foo = not signal.foo:get_value()
        end,
      },
      n.gap(1),
      n.button {
        label = ' Toggle Bar ',
        on_press = function()
          signal.bar = not signal.bar:get_value()
        end,
      },
      n.gap(1),
      n.button {
        label = ' Toggle Qux ',
        on_press = function()
          signal.qux = not signal.qux:get_value()
        end,
      }
    )
  )
)

In the above example, although I'm mapping over the foo signal value, the function seems to be called when any of the signal's values change. In fact, it seems we can map over any signal field, even if it doesn't exist, like signal.baz:map(fn).

My request: It would be nice to be able to map directly over the signal, signal:map(fn). The function fn should be called with the full value of the signal as the argument whenever any of the signal’s values change:

n.paragraph {
  is_focusable = false,
  lines = signal:map(function(val)
    return {
      n.line(n.text('Foo: ', 'Keyword'), tostring(val.foo)),
      n.line(n.text('Bar: ', 'Keyword'), tostring(val.bar)),
      n.line(n.text('Qux: ', 'Keyword'), tostring(val.qux)),
    }
  end),
},

Possibilility of dynamic shrink window height?

I want to achieve something similar to vscode and zed for lsp_document_symbols

iShot_2024-04-15_14.15.48.mp4

the main functionality is managed well but lack the dynamic size support in neovim ui.
It is possible? For various search plugins, seems only nvim-cmp has this capability.

Error: Buffer is not 'modifiable'

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>

bug: `Tree` component does not properly set CursorLine highlight to line in window

Hello! It seems like line highlighting does not work as intended when using a Tree or Tree like component such as select. I do not get CursorLine highlighting when hovering over.

Although I will say that a highlight does get applied in the component when layout is first rendered, but when it's focused the highlight gets cleared. This seems to be unexpected behavior. I will provide some screenshots below

Here is what the layout looks like on initial render.

Screenshot 2024-04-27 at 3 50 29 PM

This is what the Tree component looks like when focusing the Tree component, notice there's no CursorLine highlighting. I have made sure the theme has a CursorLine highlight as seen from the screenshot.

Screenshot 2024-04-27 at 3 52 48 PM

While I think this is unexpected behavior, If there's some context I'm missing please let me know so I can close the issue! I also apologize for the rather obnoxious screenshots

All SignalValues emit when a single value is updated

Discussed in #29

Originally posted by b0o April 17, 2024
Is this the intended behavior? I would expect a SignalValue to only emit items when it is updated, not when other values in the Signal are updated.

Demo:

2024-04-16_18-41-12_region_re.mp4
local n = require 'nui-components'

local renderer = n.create_renderer {
  width = 60,
  height = 12,
}

local signal = n.create_signal {
  name = '',
  description = '',
}

local body = function()
  return n.rows(
    n.paragraph {
      is_focusable = false,
      lines = signal.name:map(function(name)
        return {
          -- Use math.random so we can see when renders occur:
          n.line(n.text('Name: ', 'Keyword'), name .. ' (' .. math.random(1, 100) .. ')'),
        }
      end),
    },
    n.paragraph {
      is_focusable = false,
      lines = signal.description:map(function(description)
        return {
          -- Use math.random so we can see when renders occur
          n.line(n.text('Description: ', 'Keyword'), description .. ' (' .. math.random(1, 100) .. ')'),
        }
      end),
    },
    n.gap(1),
    n.form(
      { id = 'form' },
      n.text_input {
        autofocus = true,
        max_lines = 1,
        border_label = 'Name',
        on_change = function(val)
          signal.name = val
        end,
      },
      n.text_input {
        max_lines = 1,
        border_label = 'Description',
        on_change = function(val)
          signal.description = val
        end,
      }
    )
  )
end

renderer:render(body)

The following patch would make it behave in the way I would expect:

2024-04-16_18-48-14_region_re.mp4
diff --git a/lua/nui-components/signal/init.lua b/lua/nui-components/signal/init.lua
index 9446bd5..f6cdd92 100644
--- a/lua/nui-components/signal/init.lua
+++ b/lua/nui-components/signal/init.lua
@@ -47,7 +47,7 @@ function Signal.create(object)
     end,
     __newindex = function(t, key, value)
       t[self.__index][key] = value
-      self._private.subject(t[self.__index])
+      self._private.subject(t[self.__index], key)
     end,
   })
 
diff --git a/lua/nui-components/signal/value.lua b/lua/nui-components/signal/value.lua
index 90238ae..646166d 100644
--- a/lua/nui-components/signal/value.lua
+++ b/lua/nui-components/signal/value.lua
@@ -10,9 +10,13 @@ function SignalValue.create(subject, key)
     _private = {
       key = key,
       subject = subject,
-      observable = subject:map(function(value)
-        return value[key]
-      end),
+      observable = subject
+        :filter(function(_, k)
+          return k == nil or k == key
+        end)
+        :map(function(value)
+          return value[key]
+        end),
     },
   }

bug: `renderer:render()` will render blank box when called a 2nd time from a function

In my plugin I want to render the ui through a function so it can mapped to a keymap, but when the function is called a 2nd time a blank box appears.

I'm pretty sure this is a bug, but if it is not, and there's a specific way a plugin should handle spawning the UI through a function, please let me know!

steps to reproduce

  1. create a component body
  2. wrap renderer:render(body) in a function like so
local function render_this()
    renderer:render(body)
end
  1. call the function once, should look normal,
  2. close the render. (renderer:close())
  3. call the function again. Should show blank box.

visual reproduction

Screen.Recording.2024-04-25.at.8.41.56.PM.mov

the code i used in the visual reproduction

local n = require("nui-components")
local api = require("huez.api")
local utils = require("huez.utils")

local function tonode(themes)
  local nodes = {}
  for _, theme in pairs(themes) do
    local node = n.option(theme, { name = theme })
    table.insert(nodes, node)
  end
  return nodes
end

-- TODO: let create_renderer to take in a function or a table of acceptable values
local renderer = n.create_renderer({
  width = 40,
  height = vim.api.nvim_win_get_height(0),
  relative = "editor",
  -- position starts from the left corner
  position = {
    row = 0,
    col = vim.api.nvim_win_get_width(0) - 3,
  },
})

local body = n.columns(n.rows(
  { flex = 2 },
  n.prompt({
    autofocus = true,
    prefix = " ::: ",
    size = 1,
    border_label = {
      text = "󰌁 Huez",
      align = "center",
    },
  }),

  n.select({
    flex = 1,
    autofocus = false,
    border_label = "Themes",
    data = tonode(api.get_installed_themes(vim.g.huez_config.exclude)),
    on_change = function(theme)
      vim.cmd("colorscheme " .. theme.name)
    end,
    on_select = function(theme)
      api.save_colorscheme(theme.name)
      renderer:close()
      utils.log_info("Selected " .. theme.name)
    end,
  })
))

renderer:add_mappings({
  {
    mode = "n",
    key = "q",
    handler = function()
      renderer:close()
    end,
  },
})

renderer:on_unmount(function()
  vim.cmd("colorscheme " .. api.get_colorscheme())
end)

-- FIXME: how can i get this to a usercommand without making it bug out
-- renderer:render(body)

local function pick_colorscheme()
  renderer:render(body)
end

return {
  pick_colorscheme = pick_colorscheme,
}

Signal values are not being unsubscribed on component unmount

To reproduce: Update the value of a signal 100ms after the renderer unmounts.

Expected: If the SignalValue were properly unsubscribed on unmount, this should be safe, and should have no effect.

Actual: An error is thrown:

Error executing vim.schedule lua callback: nui.nvim/lua/nui/line/init.lua:72: Invalid 'buffer': Expected Lua number
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:184: 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:165: 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:398>
local n = require 'nui-components'

local renderer = n.create_renderer {
  width = 60,
  height = 12,
}

local signal = n.create_signal {
  name = '',
}

renderer:on_unmount(function()
  vim.defer_fn(function()
    signal.name = 'foo'
  end, 100)
end)

local body = function()
  return n.rows(
    n.paragraph {
      is_focusable = false,
      lines = signal.name:map(function(name)
        return {
          n.line(n.text('Name: ', 'Keyword'), name),
        }
      end),
    },
    n.gap(1),
    n.form(
      { id = 'form' },
      n.text_input {
        autofocus = true,
        max_lines = 1,
        border_label = 'Name',
        on_change = function(val)
          signal.name = val
        end,
      }
    )
  )
end

renderer:render(body)

Adding a border to a paragraph causes extra line breaks

With no border, spacing is correct:

2024-04-12_17-24-09_region

local n = require 'nui-components'

local renderer = n.create_renderer {
  width = 64,
  height = 20,
}

renderer:render(
  n.paragraph {
    lines = {
      n.line(n.text('Foo', 'Keyword')),
      n.line(n.text('Bar', 'Keyword')),
      n.line(n.text('Qux', 'Keyword')),
    },
  }
)

When adding a border, we get extra blank lines. I think there is an issue with width calculation causing the lines to wrap.

2024-04-12_17-26-00_region

local n = require 'nui-components'

local renderer = n.create_renderer {
  width = 64,
  height = 20,
}

renderer:render(
  n.paragraph {
    border_style = 'rounded',
    lines = {
      n.line(n.text('Foo', 'Keyword')),
      n.line(n.text('Bar', 'Keyword')),
      n.line(n.text('Qux', 'Keyword')),
    },
  }
)

By doing :setlocal list and :setlocal listchars=trail:~, it appears the line is padded with spaces, but there are 2 too many:

2024-04-12_22-36-23_region

Also, when truncate is set to true, the issue does not occur.

bug: n.select `on_select` field returns nil when node clearly exists

I'm trying to incorporate this library into my own plugin, but with the following file.

local function tonode(themes)
  local nodes = {}
  for _, theme in pairs(themes) do
    local node = n.option(theme, { name = theme })
    table.insert(nodes, node)
  end
  return nodes
end

-- TODO: let create_renderer to take in a function or a table of acceptable values
local renderer = n.create_renderer({
  width = 40,
  height = vim.api.nvim_win_get_height(0),
  relative = "editor",
  -- position starts from the left corner
  position = {
    row = 0,
    col = vim.api.nvim_win_get_width(0) - 3,
  },
})

local body = n.columns(n.rows(
  { flex = 2 },
  n.prompt({
    autofocus = true,
    prefix = " ::: ",
    size = 1,
    border_label = {
      text = "󰌁 Huez",
      align = "center",
    },
  }),

  n.select({
    flex = 1,
    autofocus = false,
    border_label = "Themes",
    data = tonode(api.get_installed_themes(vim.g.huez_config.exclude)), -- this api call return's a list of strings which are then cast to nodes
    on_change = function(theme)
      vim.cmd("colorscheme " .. theme.name)
    end,
    on_select = function(theme)
      api.save_colorscheme(theme.name) -- <--- the problem is here, and I've already tested that this api call works as intended in my testing suite.
      renderer:close()
      utils.log_info("Selected " .. theme.name)
    end,
  })
))

The on_change function works completely fine giving me the impression that theme.name is not nil. BUT when I select a color scheme, the on_select function will print me theme.name prints nil, I have already tested that my api's work, I'm wondering if there's anything I'm doing that's particularly wrong.

steps to reproduce

  1. create a function strings that returns a list of strings
  2. create a function to_node that takes in a list of strings as a arg and converts each string to a n.option returning an array of NuiTreeNodes
  3. copy paste the above code and replace data = ... with data = to_node(strings)
  4. Source the file and trying selecting an option

`Select` not showing proper amount of options on different renders

Hello, I've noticed unexpected behavior while using the Select component, that the amount of options presented can differ between each render throughout different sessions. Unfortunately this doesn't cause anything to error, so I have no reproducible steps.

here's a couple of screenshots showing the issue

1st time entering neovim, this is what my picker looked like. It is missing 1 option, being oxocarbon
Screenshot 2024-04-30 at 12 59 34 PM

when quitting neovim, and entering again (2nd time) I get what I expect to see al the time, a list of all installed colorschemes excluding the defaults. Notice how oxocarbon is now an option in the component
Screenshot 2024-04-30 at 12 59 46 PM

after quitting yet again and rendering (now the 3rd time) many more options are missing almost half in fact.
Screenshot 2024-04-30 at 12 59 56 PM

The way I'm getting the colorschemes is done in a synchronous manner, so I should have all of the expected options by the time the renderer renders the component tree.

Recommend Projects

  • React photo React

    A declarative, efficient, and flexible JavaScript library for building user interfaces.

  • Vue.js photo Vue.js

    🖖 Vue.js is a progressive, incrementally-adoptable JavaScript framework for building UI on the web.

  • Typescript photo Typescript

    TypeScript is a superset of JavaScript that compiles to clean JavaScript output.

  • TensorFlow photo TensorFlow

    An Open Source Machine Learning Framework for Everyone

  • Django photo Django

    The Web framework for perfectionists with deadlines.

  • D3 photo D3

    Bring data to life with SVG, Canvas and HTML. 📊📈🎉

Recommend Topics

  • javascript

    JavaScript (JS) is a lightweight interpreted programming language with first-class functions.

  • web

    Some thing interesting about web. New door for the world.

  • server

    A server is a program made to process requests and deliver data to clients.

  • Machine learning

    Machine learning is a way of modeling and interpreting data that allows a piece of software to respond intelligently.

  • Game

    Some thing interesting about game, make everyone happy.

Recommend Org

  • Facebook photo Facebook

    We are working to build community through open source technology. NB: members must have two-factor auth.

  • Microsoft photo Microsoft

    Open source projects and samples from Microsoft.

  • Google photo Google

    Google ❤️ Open Source for everyone.

  • D3 photo D3

    Data-Driven Documents codes.