Skip to content

Popupmenu

Shawon edited this page Apr 27, 2025 · 4 revisions

πŸ’» 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
Clone this wiki locally