Popupmenu - OXY2DEV/ui.nvim GitHub Wiki

💻 Pop-up menu

This file explains how the ui/cmdline.lua file works.

You can change how the pop-up menu looks by listening to the ext_popupmenu events. You will typically use something like this,

---@type integer Namespace for the UI(s).
local namespace = vim.api.nvim_create_namespace("ui");

vim.ui_attach(namespace, {
    ext_popupmenu = true
}, function (event, ...)
    --- {event}, Event name
    --- {...}, Arguments this event produces.
    --- Do stuff...
end);

📜 Event list

[!TIP] You can handle these events without using vim.schedule()!

The pop-up menu receives the following events,

  • popupmenu_show Triggered when the pop-up menu should be shown.

  • popupmenu_select Triggered when the selected item changes in the pop-up menu.

- popupmenu_hide Triggered when the pop-up menu should be hidden.

✨ Completion text

The completion menu text is shown differently based on the current mode.

📐 Single line(strip style)

This is used in command mode and is created by popup.__strip_renderer() function.

It creates a single line by iterating over the lines and applying the highlights to the regions covered by each item.

The rendering process is similar to the command-line so I won't be explaining it in detail here. It's a simple for loop that adds the completion text to a string and creates regions of highlights for that screen.

The text is then sent to nvim_buf_set_lines() and the highlights are iterated over and the values are passed to nvim_buf_set_extmark().

📐 Multi line(completion style)

This is used in other modes and is created by popup.__completion_renderer() function.

The rendering process here is a single for loop that adds a new line to the buffer(via nvim_buf_set_lines(buffer, -1, -1, false, { ... }) and an extmark for that line.

📏 Completion menu position

When using multi line pop-up menu, the menu gets placed based on the amount of free space around the cursor.

To get the cursor position on the screen we use vim.fn.screenpos(). The returned value is a table which has a property named curscol. This tells use which column the cursor is in.

It also has the row property which tells us which row in the terminal the cursor is on.

┌────────────────────────┬───────┐
│ y = Screen row         │       │
│ x = Screen column      y       │
│                        │       │
│                        │       │
├────────────x───────────█──w─┬──┤
│ w = Menu width         │░░░░┆  │
│ h = Menu height        h░░░░┆  │
│                        │░░░░┆  │
│                        ├┄┄┄┄┘  │
│                        │       │
└────────────────────────┴───────┘

A simple condition is used to check which side to open the menu on. It looks like this,

-- Floating window's anchor based on
-- where it should be opened.
--
-- SE | SW
-- ---+---
-- NE | NW

---@type "NE" | "NW" | "SE" | "SW"
local anchor;

if y + h >= vim.o.lines then
	-- Above
    anchor = "S";
else
	-- Below
    anchor = "N";
end

if x + w >= vim.o.columns then
	-- Left
    anchor = anchor .. "E";
else
	-- Right
    anchor = anchor .. "W";
end