nui.nvim

repository·main·Indexed 24 days ago

https://github.com/muniftanjim/nui.nvim

A UI component library for Neovim providing high-level abstractions for building complex user interfaces. It includes components for popups, layouts, menus, and structured text blocks, such as the Input component for prompt buffers, the Layout component for arranging nested elements via Layout.Box, and NuiLine for managing multi-text lines with highlights.

Tokens
11.8K
Snippets
26
Records
51
Agent score
83%

What's inside nui.nvim

  1. Use NuiText to manage text and highlights

    main

    NuiText is an abstraction layer over Neovim's native nvim_buf_set_text and nvim_buf_set_extmark functions. It simplifies setting text content and applying highlights to specific buffer locations.

    Initialization

    You can create a NuiText object using the following signature: NuiText(content, extmark?)

    • content: A string or table containing the text content. If a NuiText object is passed, a copy is created.
    • extmark: Defines the highlight.
      • If a string is passed, it is treated as the highlight group name.
      • If a table is passed, it is treated as extmark options. It supports the key "hl_group" for the highlight group name.

    Rendering to a Buffer

    To actually place the text in a buffer, use the render or render_char methods.

  2. Configure Popup positioning and relativity

    main

    Popups use relative, position, size, and anchor to determine where they appear on the screen.

    Relativity (relative)

    Determines the reference point for position and size calculations:

    • "cursor": Relative to the cursor in the current window.
    • "editor": Relative to the entire editor screen.
    • "win" (default): Relative to the current window.
    • table with type = "win" and winid: Relative to a specific window ID.
    • table with type = "buf" and position = { row, col }: Relative to a specific buffer position.

    Position (position)

    Calculated from the top-left corner of the relative reference. Can be a number, a percentage string (e.g., "50%"), or a table with row and col keys. Note: If relative is "buf" or "cursor", percentage strings are not allowed.

    Size (size)

    Determines the dimensions. Can be a number, a percentage string, or a table with width and height. A decimal number in the (0,1) range (e.g., 0.5) is treated as a percentage.

    Anchor (anchor)

    Decides which corner of the popup is placed at the calculated position. Valid values: "NW", "NE", "SW", "SE".

    position = {
      row = "20%",
      col = "50%",
    },
  3. Extend existing components

    main

    You can extend any block or component using the :extend() method to add new methods or override behaviors. This uses a class-based system similar to middleclass. When extending, you can call self.super.init(self, options) to invoke the parent constructor.

    local Timer = Popup:extend("Timer")
    
    function Timer:init(popup_options)
      local options = vim.tbl_deep_extend("force", popup_options or {}, {
        border = "double",
        focusable = false,
        position = { row = 0, col = "100%" },
        size = { width = 10, height = 1 },
        win_options = {
          winhighlight = "Normal:Normal,FloatBorder:SpecialChar",
        },
      })
    
      Timer.super.init(self, options)
    end
    
    function Timer:countdown(time, step, format)
      local function draw_content(text)
        local gap_width = 10 - vim.api.nvim_strwidth(text)
        vim.api.nvim_buf_set_lines(self.bufnr, 0, -1, false, {
          string.format(
            "%s%s%s",
            string.rep(" ", math.floor(gap_width / 2)),
            text,
            string.rep(" ", math.ceil(gap_width / 2))
          ),
        })
      end
    
      self:mount()
    
      local remaining_time = time
    
      draw_content(format(remaining_time))
    
      vim.fn.timer_start(step, function()
        remaining_time = remaining_time - step
    
        draw_content(format(remaining_time))
    
        if remaining_time <= 0 then
          self:unmount()
        end
      end, { ["repeat"] = math.ceil(remaining_time / step) })
    end
    
    local timer = Timer()
    
    timer:countdown(10000, 1000, function(time)
      return tostring(time / 1000) .. "s"
    end)
  4. Use the Input component

    main

    The nui.input component is an abstraction layer built on top of nui.popup. It utilizes a prompt buffer for its window.

    To create an input, call require("nui.input") with two arguments:

    1. Popup Options: A table containing options for the underlying nui.popup component (e.g., relative, position, size, border, win_options).
    2. Input Options: A table containing input-specific configuration like prompt, default_value, and lifecycle callbacks.

    Key behaviors:

    • Pressing <CR> triggers on_submit and closes the window.
    • Pressing <C-c> triggers on_close and closes the window.
    • You can access the underlying buffer and window via input.bufnr and input.winid.
    local Input = require("nui.input")
    
    local popup_options = {
      relative = "cursor",
      position = {
        row = 1,
        col = 0,
      },
      size = 20,
      border = {
        style = "rounded",
        text = {
          top = "[Input]",
          top_align = "left",
        },
      },
      win_options = {
        winhighlight = "Normal:Normal",
      },
    }
    
    local input = Input(popup_options, {
      prompt = "> ",
      default_value = "42",
      on_close = function()
        print("Input closed!")
      end,
      on_submit = function(value)
        print("Value submitted: ", value)
      end,
      on_change = function(value)
        print("Value changed: ", value)
      end,
    })
  5. Create a menu with nui.menu

    main

    The Menu component is an abstraction layer built on top of nui.popup. To create a menu, call require("nui.menu") with two arguments:

    1. popup_options: Options for the underlying nui.popup component (e.g., relative, position, border, win_options).
    2. menu_options: Configuration for the menu items, dimensions, keymaps, and callbacks.

    You can access the underlying buffer and window via the split.bufnr and split.winid properties.

    local Menu = require("nui.menu")
    
    local popup_options = {
      relative = "cursor",
      position = { row = 1, col = 0 },
      border = { style = "rounded", text = { top = "[Choose Item]", top_align = "center" } },
      win_options = { winhighlight = "Normal:Normal" }
    }
    
    local menu = Menu(popup_options, {
      lines = {
        Menu.separator("Group One"),
        Menu.item("Item 1"),
        Menu.item("Item 2"),
        Menu.separator("Group Two", { char = "-", text_align = "right" }),
        Menu.item("Item 3"),
        Menu.item("Item 4"),
      },
      max_width = 20,
      keymap = {
        focus_next = { "j", "<Down>", "<Tab>" },
        focus_prev = { "k", "<Up>", "<S-Tab>" },
        close = { "<Esc>", "<C-c>" },
        submit = { "<CR>", "<Space>" },
      },
      on_close = function()
        print("CLOSED")
      end,
      on_submit = function(item)
        print("SUBMITTED", vim.inspect(item))
      end,
    })
  6. Use Split to split windows or the editor

    main

    The nui.split module allows you to split the current window or the entire editor into new sections. You can access the resulting buffer and window IDs via the bufnr and winid properties of the split instance.

    local Split = require("nui.split")
    
    local split = Split({
      relative = "editor",
      position = "bottom",
      size = "20%",
    })
    
    -- Access the buffer and window
    print(split.bufnr)
    print(split.winid)
  7. Use NuiLine to create multi-text lines

    main

    NuiLine is an abstraction layer that allows you to create a single line in a buffer composed of multiple NuiText objects. It wraps native Neovim functions like nvim_buf_set_lines, nvim_buf_set_text, and nvim_buf_add_highlight to manage content and styling easily.

    To use NuiLine, you can initialize it empty or pass a list of NuiText objects as initial content.

    local NuiLine = require("nui.line")
    
    -- Initialize empty
    local line = NuiLine()
    
    -- Or initialize with NuiText objects
    local text_one = NuiText("One")
    local text_two = NuiText("Two")
    local line = NuiLine({ text_one, text_two })
  8. Create a tree with NuiTree

    main

    Use NuiTree to render tree-like structured content in a Neovim buffer. You initialize it with a list of NuiTree.Node objects and then call :render() to display them.

    local NuiTree = require("nui.tree")
    
    local tree = NuiTree({
      bufnr = bufnr,
      nodes = {
        NuiTree.Node({ text = "a" }),
        NuiTree.Node({ text = "b" }, {
          NuiTree.Node({ text = "b-1" }),
          NuiTree.Node({ text = { "b-2", "b-3" } }),
        }),
      },
    })
    
    tree:render()
  9. Configure Popup borders and padding

    main

    The border option (type table) allows for extensive styling of the popup's perimeter.

    Padding

    border.padding controls the space between the border and the content. It supports two formats:

    1. List (CSS-style): A table with [top, right, bottom, left] values. Example: { 1, 2 } sets top/bottom to 1 and left/right to 2.
    2. Map: A table with named sides. Example: { top = 1, left = 2 }.

    Style

    border.style defines the visual appearance of the border. You can provide:

    • Pre-defined strings: "double", "none", "rounded", "shadow", "single", "solid", or "default".
    • Character List: A table of characters starting from top-left and moving clockwise.
    • Character Map: A table with named keys like top_left, top, top_right, etc.
    • Direct assignment: You can pass the style string directly to border instead of border.style.

    To style the border characters, use win_options.winhighlight with FloatBorder, or use NuiText / (char, hl_group) tuples within the style table.

    -- Example: Custom character list with highlighting
    border = {
      style = { { [[/]], "SpecialChar" }, [[─]], NuiText([[\]], "SpecialChar"), [[│]] },
    },
    border = {
      -- `1` for top/bottom and `2` for left/right
      padding = { 1, 2 },
    },
  10. Configure Float Layout options

    main

    When creating a float layout, you can use the following options to control its placement and dimensions:

    anchor

    Determines which corner of the layout is placed at the position. Values: "NW", "NE", "SW", "SE".

    relative

    Defines the reference point for position and size calculations.

    • "cursor": Relative to the cursor in the current window.
    • "editor": Relative to the entire editor screen.
    • "win" (default): Relative to the current window.
    • table:
      • { type = "win", winid = <id> }: Relative to a specific window ID.
      • { type = "buf", position = { row = <n>, col = <n> } }: Relative to a buffer position (zero-indexed). Note: percentage string is not allowed when using "buf" or "cursor".

    position

    Calculates the top-left corner position. Can be a number, a percentage string (e.g., "50%"), or a table specifying row and col separately. Note: If relative is "buf" or "cursor", you cannot use percentage strings.

    size

    Determines the layout dimensions. Can be a number, a percentage string, a decimal number in range (0,1) (e.g., 0.5 is "50%"), or a table specifying width and height separately.

  11. Configure Popup behavior and window/buffer options

    main

    Use the following options to control how the popup behaves and how its underlying components are configured:

    • enter (boolean): If true, the popup is entered immediately after mounting.
    • focusable (boolean): If false, the popup cannot be entered via wincmds or mouse events.
    • zindex (number): Sets the Z-axis order. Higher values appear on top of lower values.
    • ns_id (number | string): The Namespace ID or name.
    • buf_options (table): Pass standard Neovim buffer-local options (e.g., { modifiable = true, readonly = false }).
    • win_options (table): Pass standard Neovim window-local options (eg. { winblend = 10, winhighlight = "Normal:Normal" }).
    • bufnr (number): Pass an existing buffer ID to display that buffer inside the popup.
    -- Example: Using an existing buffer
    bufnr = vim.api.nvim_get_current_buf(),