Meh I'll figure out submodules later
This commit is contained in:
parent
4ca9d44a90
commit
8cb281f436
352 changed files with 66107 additions and 0 deletions
674
.config/nvim/pack/tree/start/nui.nvim/lua/nui/popup/README.md
Normal file
674
.config/nvim/pack/tree/start/nui.nvim/lua/nui/popup/README.md
Normal file
|
|
@ -0,0 +1,674 @@
|
|||
# Popup
|
||||
|
||||
Popup is an abstraction layer on top of window.
|
||||
|
||||
Creates a new popup object (but does not mount it immediately).
|
||||
|
||||
**Examples**
|
||||
|
||||
```lua
|
||||
local Popup = require("nui.popup")
|
||||
|
||||
local popup = Popup({
|
||||
position = "50%",
|
||||
size = {
|
||||
width = 80,
|
||||
height = 40,
|
||||
},
|
||||
enter = true,
|
||||
focusable = true,
|
||||
zindex = 50,
|
||||
relative = "editor",
|
||||
border = {
|
||||
padding = {
|
||||
top = 2,
|
||||
bottom = 2,
|
||||
left = 3,
|
||||
right = 3,
|
||||
},
|
||||
style = "rounded",
|
||||
text = {
|
||||
top = " I am top title ",
|
||||
top_align = "center",
|
||||
bottom = "I am bottom title",
|
||||
bottom_align = "left",
|
||||
},
|
||||
},
|
||||
buf_options = {
|
||||
modifiable = true,
|
||||
readonly = false,
|
||||
},
|
||||
win_options = {
|
||||
winblend = 10,
|
||||
winhighlight = "Normal:Normal,FloatBorder:FloatBorder",
|
||||
},
|
||||
})
|
||||
```
|
||||
|
||||
You can manipulate the associated buffer and window using the
|
||||
`split.bufnr` and `split.winid` properties.
|
||||
|
||||
## Options
|
||||
|
||||
### `border`
|
||||
|
||||
**Type:** `table`
|
||||
|
||||
Contains all border related options.
|
||||
|
||||
#### `border.padding`
|
||||
|
||||
**Type:** `table`
|
||||
|
||||
Controls the popup padding.
|
||||
|
||||
**Examples**
|
||||
|
||||
It can be a list (`table`) with number of cells for top, right, bottom and left.
|
||||
The order behaves like [CSS](https://developer.mozilla.org/en-US/docs/Web/CSS) padding.
|
||||
|
||||
```lua
|
||||
border = {
|
||||
-- `1` for top/bottom and `2` for left/right
|
||||
padding = { 1, 2 },
|
||||
},
|
||||
```
|
||||
|
||||
You can also use a map (`table`) to set padding at specific side:
|
||||
|
||||
```lua
|
||||
border = {
|
||||
-- `1` for top, `2` for left, `0` for other sides
|
||||
padding = {
|
||||
top = 1,
|
||||
left = 2,
|
||||
},
|
||||
},
|
||||
```
|
||||
|
||||
#### `border.style`
|
||||
|
||||
**Type:** `string` or `table`
|
||||
|
||||
Controls the styling of the border.
|
||||
|
||||
**Examples**
|
||||
|
||||
Can be one of the pre-defined styles: `"double"`, `"none"`, `"rounded"`, `"shadow"`, `"single"`, `"solid"` or `"default"`.
|
||||
|
||||
```lua
|
||||
border = {
|
||||
style = "double",
|
||||
},
|
||||
```
|
||||
|
||||
List (`table`) of characters starting from the top-left corner and then clockwise:
|
||||
|
||||
```lua
|
||||
border = {
|
||||
style = { "╭", "─", "╮", "│", "╯", "─", "╰", "│" },
|
||||
},
|
||||
```
|
||||
|
||||
Map (`table`) with named characters:
|
||||
|
||||
```lua
|
||||
border = {
|
||||
style = {
|
||||
top_left = "╭", top = "─", top_right = "╮",
|
||||
left = "│", right = "│",
|
||||
bottom_left = "╰", bottom = "─", bottom_right = "╯",
|
||||
},
|
||||
},
|
||||
```
|
||||
|
||||
If you don't need all these options, you can also pass the value of `border.style` to `border`
|
||||
directly.
|
||||
|
||||
To set the highlight group for all the border characters, use the `win_options.winhighlight`
|
||||
option and include the name of highlight group for `FloatBorder`.
|
||||
|
||||
**Examples**
|
||||
|
||||
```lua
|
||||
win_options = {
|
||||
winhighlight = "Normal:Normal,FloatBorder:SpecialChar",
|
||||
},
|
||||
```
|
||||
|
||||
To set the highlight group for individual border character, you can use `NuiText` or a tuple
|
||||
with `(char, hl_group)`.
|
||||
|
||||
**Examples**
|
||||
|
||||
```lua
|
||||
border = {
|
||||
style = { { [[/]], "SpecialChar" }, [[─]], NuiText([[\]], "SpecialChar"), [[│]] },
|
||||
},
|
||||
```
|
||||
|
||||
#### `border.text`
|
||||
|
||||
**Type:** `table`
|
||||
|
||||
Text displayed on the border (as title/footnote).
|
||||
|
||||
| Key | Type | Description |
|
||||
| ---------------- | -------------------------------------------- | ---------------------------- |
|
||||
| `"top"` | `string` / `NuiLine` / `NuiText` | top border text |
|
||||
| `"top_align"` | `"left"` / `"right"`/ `"center"` _(default)_ | top border text alignment |
|
||||
| `"bottom"` | `string` / `NuiLine` / `NuiText` | bottom border text |
|
||||
| `"bottom_align"` | `"left"` / `"right"`/ `"center"` _(default)_ | bottom border text alignment |
|
||||
|
||||
`"top"` and `"bottom"` also supports list of `(text, hl_group)` tuples, just like the native popup.
|
||||
|
||||
**Examples**
|
||||
|
||||
```lua
|
||||
border = {
|
||||
text = {
|
||||
top = "Popup Title",
|
||||
top_align = "center",
|
||||
},
|
||||
},
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
### `ns_id`
|
||||
|
||||
**Type:** `number` or `string`
|
||||
|
||||
Namespace id (`number`) or name (`string`).
|
||||
|
||||
---
|
||||
|
||||
### `anchor`
|
||||
|
||||
**Type:** `"NW"` / `"NE"` / `"SW"` / `"SE"`
|
||||
|
||||
Decides which corner of the popup to place at `position`.
|
||||
|
||||
---
|
||||
|
||||
### `relative`
|
||||
|
||||
**Type:** `string` or `table`
|
||||
|
||||
This option affects how `position` and `size` are calculated.
|
||||
|
||||
**Examples**
|
||||
|
||||
Relative to cursor on current window:
|
||||
|
||||
```lua
|
||||
relative = "cursor",
|
||||
```
|
||||
|
||||
Relative to the current editor screen:
|
||||
|
||||
```lua
|
||||
relative = "editor",
|
||||
```
|
||||
|
||||
Relative to the current window (_default_):
|
||||
|
||||
```lua
|
||||
relative = "win",
|
||||
```
|
||||
|
||||
Relative to the window with specific id:
|
||||
|
||||
```lua
|
||||
relative = {
|
||||
type = "win",
|
||||
winid = 5,
|
||||
},
|
||||
```
|
||||
|
||||
Relative to the buffer position:
|
||||
|
||||
```lua
|
||||
relative = {
|
||||
type = "buf",
|
||||
-- zero-indexed
|
||||
position = {
|
||||
row = 5,
|
||||
col = 5,
|
||||
},
|
||||
},
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
### `position`
|
||||
|
||||
**Type:** `number` or `percentage string` or `table`
|
||||
|
||||
Position is calculated from the top-left corner.
|
||||
|
||||
If `position` is `number` or `percentage string`, it applies to both `row` and `col`.
|
||||
Or you can pass a table to set them separately.
|
||||
|
||||
For `percentage string`, position is calculated according to the option `relative`.
|
||||
If `relative` is set to `"buf"` or `"cursor"`, `percentage string` is not allowed.
|
||||
|
||||
**Examples**
|
||||
|
||||
```lua
|
||||
position = 50,
|
||||
```
|
||||
|
||||
```lua
|
||||
position = "50%",
|
||||
```
|
||||
|
||||
```lua
|
||||
position = {
|
||||
row = 30,
|
||||
col = 20,
|
||||
},
|
||||
```
|
||||
|
||||
```lua
|
||||
position = {
|
||||
row = "20%",
|
||||
col = "50%",
|
||||
},
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
### `size`
|
||||
|
||||
**Type:** `number` or `percentage string` or `table`
|
||||
|
||||
Determines the size of the popup.
|
||||
|
||||
If `size` is `number` or `percentage string`, it applies to both `width` and `height`.
|
||||
You can also pass a table to set them separately.
|
||||
|
||||
For `percentage string`, `size` is calculated according to the option `relative`.
|
||||
If `relative` is set to `"buf"` or `"cursor"`, window size is considered.
|
||||
|
||||
Decimal `number` in `(0,1)` range is treated similar to `percentage string`. For
|
||||
example: `0.5` is same as `"50%"`.
|
||||
|
||||
**Examples**
|
||||
|
||||
```lua
|
||||
size = 50,
|
||||
```
|
||||
|
||||
```lua
|
||||
size = "50%",
|
||||
```
|
||||
|
||||
```lua
|
||||
size = 0.5,
|
||||
```
|
||||
|
||||
```lua
|
||||
size = {
|
||||
width = 80,
|
||||
height = 40,
|
||||
},
|
||||
```
|
||||
|
||||
```lua
|
||||
size = {
|
||||
width = "80%",
|
||||
height = 0.6,
|
||||
},
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
### `enter`
|
||||
|
||||
**Type:** `boolean`
|
||||
|
||||
If `true`, the popup is entered immediately after mount.
|
||||
|
||||
**Examples**
|
||||
|
||||
```lua
|
||||
enter = true,
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
### `focusable`
|
||||
|
||||
**Type:** `boolean`
|
||||
|
||||
If `false`, the popup can not be entered by user actions (wincmds, mouse events).
|
||||
|
||||
**Examples**
|
||||
|
||||
```lua
|
||||
focusable = true,
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
### `zindex`
|
||||
|
||||
**Type:** `number`
|
||||
|
||||
Sets the order of the popup on z-axis.
|
||||
|
||||
Popup with higher the `zindex` goes on top of popups with lower `zindex`.
|
||||
|
||||
**Examples**
|
||||
|
||||
```lua
|
||||
zindex = 50,
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
### `buf_options`
|
||||
|
||||
**Type:** `table`
|
||||
|
||||
Contains all buffer related options (check `:h options | /local to buffer`).
|
||||
|
||||
**Examples**
|
||||
|
||||
```lua
|
||||
buf_options = {
|
||||
modifiable = false,
|
||||
readonly = true,
|
||||
},
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
### `win_options`
|
||||
|
||||
**Type:** `table`
|
||||
|
||||
Contains all window related options (check `:h options | /local to window`).
|
||||
|
||||
**Examples**
|
||||
|
||||
```lua
|
||||
win_options = {
|
||||
winblend = 10,
|
||||
winhighlight = "Normal:Normal,FloatBorder:FloatBorder",
|
||||
},
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
### `bufnr`
|
||||
|
||||
**Type:** `number`
|
||||
|
||||
You can pass `bufnr` of an existing buffer to display it on the popup.
|
||||
|
||||
**Examples:**
|
||||
|
||||
```lua
|
||||
bufnr = vim.api.nvim_get_current_buf(),
|
||||
```
|
||||
|
||||
## Methods
|
||||
|
||||
### `popup:mount`
|
||||
|
||||
_Signature:_ `popup:mount()`
|
||||
|
||||
Mounts the popup.
|
||||
|
||||
**Examples**
|
||||
|
||||
```lua
|
||||
popup:mount()
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
### `popup:unmount`
|
||||
|
||||
_Signature:_ `popup:unmount()`
|
||||
|
||||
Unmounts the popup.
|
||||
|
||||
**Examples**
|
||||
|
||||
```lua
|
||||
popup:unmount()
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
### `popup:hide`
|
||||
|
||||
_Signature:_ `popup:hide()`
|
||||
|
||||
Hides the popup window. Preserves the buffer (related content, autocmds and keymaps).
|
||||
|
||||
---
|
||||
|
||||
### `popup:show`
|
||||
|
||||
_Signature:_ `popup:show()`
|
||||
|
||||
Shows the hidden popup window.
|
||||
|
||||
---
|
||||
|
||||
### `popup:map`
|
||||
|
||||
_Signature:_ `popup:map(mode, key, handler, opts) -> nil`
|
||||
|
||||
Sets keymap for the popup.
|
||||
|
||||
**Parameters**
|
||||
|
||||
| Name | Type | Description |
|
||||
| --------- | --------------------- | --------------------------------------------------------------------------- |
|
||||
| `mode` | `string` | check `:h :map-modes` |
|
||||
| `key` | `string` | key for the mapping |
|
||||
| `handler` | `string` / `function` | handler for the mapping |
|
||||
| `opts` | `table` | check `:h :map-arguments` (including `remap`/`noremap`, excluding `buffer`) |
|
||||
|
||||
**Examples**
|
||||
|
||||
```lua
|
||||
local ok = popup:map("n", "<esc>", function(bufnr)
|
||||
print("ESC pressed in Normal mode!")
|
||||
end, { noremap = true })
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
### `popup:unmap`
|
||||
|
||||
_Signature:_ `popup:unmap(mode, key) -> nil`
|
||||
|
||||
Deletes keymap for the popup.
|
||||
|
||||
**Parameters**
|
||||
|
||||
| Name | Type | Description |
|
||||
| ------ | ------------- | --------------------- |
|
||||
| `mode` | `"n"` / `"i"` | check `:h :map-modes` |
|
||||
| `key` | `string` | key for the mapping |
|
||||
|
||||
**Examples**
|
||||
|
||||
```lua
|
||||
local ok = popup:unmap("n", "<esc>")
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
### `popup:on`
|
||||
|
||||
_Signature:_ `popup:on(event, handler, options)`
|
||||
|
||||
Defines `autocmd` to run on specific events for this popup.
|
||||
|
||||
**Parameters**
|
||||
|
||||
| Name | Type | Description |
|
||||
| --------- | --------------------- | ------------------------------------------ |
|
||||
| `event` | `string[]` / `string` | check `:h events` |
|
||||
| `handler` | `function` | handler function for event |
|
||||
| `options` | `table` | keys `once`, `nested` and values `boolean` |
|
||||
|
||||
**Examples**
|
||||
|
||||
```lua
|
||||
local event = require("nui.utils.autocmd").event
|
||||
|
||||
popup:on({ event.BufLeave }, function()
|
||||
popup:unmount()
|
||||
end, { once = true })
|
||||
```
|
||||
|
||||
`event` can be expressed as any of the followings:
|
||||
|
||||
```lua
|
||||
{ event.BufLeave, event.BufDelete }
|
||||
-- or
|
||||
{ event.BufLeave, "BufDelete" }
|
||||
-- or
|
||||
event.BufLeave
|
||||
-- or
|
||||
"BufLeave"
|
||||
-- or
|
||||
"BufLeave,BufDelete"
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
### `popup:off`
|
||||
|
||||
_Signature:_ `popup:off(event)`
|
||||
|
||||
Removes `autocmd` defined with `popup:on({ ... })`
|
||||
|
||||
**Parameters**
|
||||
|
||||
| Name | Type | Description |
|
||||
| ------- | --------------------- | ----------------- |
|
||||
| `event` | `string[]` / `string` | check `:h events` |
|
||||
|
||||
**Examples**
|
||||
|
||||
```lua
|
||||
popup:off("*")
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
### `popup:update_layout`
|
||||
|
||||
_Signature:_ `popup:update_layout(config)`
|
||||
|
||||
Sets the layout of the popup. You can use this method to change popup's
|
||||
size or position after it's mounted.
|
||||
|
||||
**Parameters**
|
||||
|
||||
`config` is a `table` having the following keys:
|
||||
|
||||
| Key | Type |
|
||||
| ---------- | --------------------------------- |
|
||||
| `anchor` | `"NW"` / `"NE"` / `"SW"` / `"SE"` |
|
||||
| `relative` | `string` / `table` |
|
||||
| `position` | `string` / `table` |
|
||||
| `size` | `string` / `table` |
|
||||
|
||||
They are the same options used for popup initialization.
|
||||
|
||||
**Examples**
|
||||
|
||||
```lua
|
||||
popup:update_layout({
|
||||
relative = "win",
|
||||
size = {
|
||||
width = 80,
|
||||
height = 40,
|
||||
},
|
||||
position = {
|
||||
row = 30,
|
||||
col = 20,
|
||||
},
|
||||
})
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
### `popup.border:set_highlight`
|
||||
|
||||
_Signature:_ `popup.border:set_highlight(highlight: string) -> nil`
|
||||
|
||||
Sets border highlight.
|
||||
|
||||
**Parameters**
|
||||
|
||||
| Name | Type | Description |
|
||||
| ----------- | -------- | -------------------- |
|
||||
| `highlight` | `string` | highlight group name |
|
||||
|
||||
**Examples**
|
||||
|
||||
```lua
|
||||
popup.border:set_highlight("SpecialChar")
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
### `popup.border:set_style`
|
||||
|
||||
_Signature:_ `popup.border:set_style(style: string|table) -> nil`
|
||||
|
||||
Sets border style.
|
||||
|
||||
**Parameters**
|
||||
|
||||
| Name | Type | Description |
|
||||
| ------- | ------------------ | ------------ |
|
||||
| `style` | `string` / `table` | border style |
|
||||
|
||||
This `style` parameter is exactly the same as popup option `border.style`.
|
||||
|
||||
You'll need to call `popup:update_layout()` after this for the change to render on screen.
|
||||
|
||||
**Examples**
|
||||
|
||||
```lua
|
||||
popup.border:set_style("rounded")
|
||||
popup:update_layout()
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
### `popup.border:set_text`
|
||||
|
||||
_Signature:_ `popup.border:set_text(edge, text, align)`
|
||||
|
||||
Sets border text.
|
||||
|
||||
**Parameters**
|
||||
|
||||
| Name | Type |
|
||||
| ------- | ------------------------------------------- |
|
||||
| `edge` | `"top"` / `"bottom"` / `"left"` / `"right"` |
|
||||
| `text` | `string` |
|
||||
| `align` | `"left"` / `"right"`/ `"center"` |
|
||||
|
||||
**Examples**
|
||||
|
||||
```lua
|
||||
popup.border:set_text("bottom", "[Progress: 42%]", "right")
|
||||
```
|
||||
|
||||
## Wiki Page
|
||||
|
||||
You can find additional documentation/examples/guides/tips-n-tricks in [nui.popup wiki page](https://github.com/MunifTanjim/nui.nvim/wiki/nui.popup).
|
||||
746
.config/nvim/pack/tree/start/nui.nvim/lua/nui/popup/border.lua
Normal file
746
.config/nvim/pack/tree/start/nui.nvim/lua/nui/popup/border.lua
Normal file
|
|
@ -0,0 +1,746 @@
|
|||
---@diagnostic disable: invisible
|
||||
|
||||
local Object = require("nui.object")
|
||||
local Line = require("nui.line")
|
||||
local Text = require("nui.text")
|
||||
local _ = require("nui.utils")._
|
||||
local is_type = require("nui.utils").is_type
|
||||
|
||||
local has_nvim_0_5_1 = vim.fn.has("nvim-0.5.1") == 1
|
||||
local has_nvim_0_11_0 = _.feature.v0_11
|
||||
|
||||
local index_name = {
|
||||
"top_left",
|
||||
"top",
|
||||
"top_right",
|
||||
"right",
|
||||
"bottom_right",
|
||||
"bottom",
|
||||
"bottom_left",
|
||||
"left",
|
||||
}
|
||||
|
||||
---@param border _nui_popup_border_style_list
|
||||
---@return _nui_popup_border_style_map
|
||||
local function to_border_map(border)
|
||||
local count = vim.tbl_count(border) --[[@as integer]]
|
||||
if count < 8 then
|
||||
-- fillup all 8 characters
|
||||
for i = count + 1, 8 do
|
||||
local fallback_index = i % count
|
||||
local char = border[fallback_index == 0 and count or fallback_index]
|
||||
if type(char) == "table" then
|
||||
char = char.content and Text(char) or vim.deepcopy(char)
|
||||
end
|
||||
border[i] = char
|
||||
end
|
||||
end
|
||||
|
||||
---@type _nui_popup_border_style_map
|
||||
local named_border = {}
|
||||
for index, name in ipairs(index_name) do
|
||||
named_border[name] = border[index]
|
||||
end
|
||||
return named_border
|
||||
end
|
||||
|
||||
---@param char _nui_popup_border_style_map
|
||||
---@return _nui_popup_border_internal_char
|
||||
local function normalize_char_map(char)
|
||||
if not char or type(char) == "string" then
|
||||
return char
|
||||
end
|
||||
|
||||
for position, item in pairs(char) do
|
||||
if type(item) == "string" then
|
||||
char[position] = Text(item, "FloatBorder")
|
||||
elseif not item.content then
|
||||
char[position] = Text(item[1], item[2] or "FloatBorder")
|
||||
elseif item.extmark then
|
||||
item.extmark.hl_group = item.extmark.hl_group or "FloatBorder"
|
||||
else
|
||||
item.extmark = { hl_group = "FloatBorder" }
|
||||
end
|
||||
end
|
||||
|
||||
return char --[[@as _nui_popup_border_internal_char]]
|
||||
end
|
||||
|
||||
---@param char? NuiText
|
||||
---@return boolean
|
||||
local function is_empty_char(char)
|
||||
return not char or 0 == char:width()
|
||||
end
|
||||
|
||||
---@param text? _nui_popup_border_option_text_value
|
||||
---@return nil|NuiLine|NuiText
|
||||
local function normalize_border_text(text)
|
||||
if not text then
|
||||
return text
|
||||
end
|
||||
|
||||
if type(text) == "string" then
|
||||
return Text(text, "FloatTitle")
|
||||
end
|
||||
|
||||
if text.content then
|
||||
for _, text_chunk in ipairs(text._texts or { text }) do
|
||||
text_chunk.extmark = vim.tbl_deep_extend("keep", text_chunk.extmark or {}, {
|
||||
hl_group = "FloatTitle",
|
||||
})
|
||||
end
|
||||
return text --[[@as NuiLine|NuiText]]
|
||||
end
|
||||
|
||||
local line = Line()
|
||||
for _, chunk in ipairs(text) do
|
||||
if type(chunk) == "string" then
|
||||
line:append(chunk, "FloatTitle")
|
||||
else
|
||||
line:append(chunk[1], chunk[2] or "FloatTitle")
|
||||
end
|
||||
end
|
||||
return line
|
||||
end
|
||||
|
||||
---@param internal nui_popup_border_internal
|
||||
---@param popup_winhighlight? string
|
||||
---@return nil|string
|
||||
local function calculate_winhighlight(internal, popup_winhighlight)
|
||||
if internal.type == "simple" then
|
||||
return
|
||||
end
|
||||
|
||||
local winhl = popup_winhighlight
|
||||
|
||||
-- @deprecated
|
||||
if internal.highlight then
|
||||
if not string.match(internal.highlight, ":") then
|
||||
internal.highlight = "FloatBorder:" .. internal.highlight
|
||||
end
|
||||
|
||||
winhl = internal.highlight
|
||||
internal.highlight = nil
|
||||
end
|
||||
|
||||
return winhl
|
||||
end
|
||||
|
||||
---@param padding? nui_popup_border_option_padding
|
||||
---@return nil|nui_popup_border_internal_padding
|
||||
local function normalize_option_padding(padding)
|
||||
if not padding then
|
||||
return nil
|
||||
end
|
||||
|
||||
if is_type("map", padding) then
|
||||
---@cast padding _nui_popup_border_option_padding_map
|
||||
return padding
|
||||
end
|
||||
|
||||
local map = {}
|
||||
|
||||
---@cast padding _nui_popup_border_option_padding_list
|
||||
map.top = padding[1] or 0
|
||||
map.right = padding[2] or map.top
|
||||
map.bottom = padding[3] or map.top
|
||||
map.left = padding[4] or map.right
|
||||
|
||||
return map
|
||||
end
|
||||
|
||||
---@param text? nui_popup_border_option_text
|
||||
---@return nil|nui_popup_border_internal_text
|
||||
local function normalize_option_text(text)
|
||||
if not text then
|
||||
return text
|
||||
end
|
||||
|
||||
text.top = normalize_border_text(text.top)
|
||||
text.bottom = normalize_border_text(text.bottom)
|
||||
|
||||
return text --[[@as nui_popup_border_internal_text]]
|
||||
end
|
||||
|
||||
---@param edge 'top'|'bottom'
|
||||
---@param text? NuiLine|NuiText
|
||||
---@param align? nui_t_text_align
|
||||
---@return NuiLine
|
||||
local function calculate_buf_edge_line(internal, edge, text, align)
|
||||
local char, size = internal.char, internal.size
|
||||
|
||||
local left_char = char[edge .. "_left"]
|
||||
local mid_char = char[edge]
|
||||
local right_char = char[edge .. "_right"]
|
||||
|
||||
if left_char:content() == "" then
|
||||
left_char = Text(mid_char:content() == "" and char["left"] or mid_char)
|
||||
end
|
||||
|
||||
if right_char:content() == "" then
|
||||
right_char = Text(mid_char:content() == "" and char["right"] or mid_char)
|
||||
end
|
||||
|
||||
local max_width = size.width - left_char:width() - right_char:width()
|
||||
|
||||
local content = Line()
|
||||
if mid_char:width() == 0 then
|
||||
content:append(string.rep(" ", max_width))
|
||||
else
|
||||
content:append(text or "")
|
||||
end
|
||||
|
||||
_.truncate_nui_line(content, max_width)
|
||||
|
||||
local left_gap_width, right_gap_width = _.calculate_gap_width(align or "center", max_width, content:width())
|
||||
|
||||
local line = Line()
|
||||
|
||||
line:append(left_char)
|
||||
|
||||
if left_gap_width > 0 then
|
||||
line:append(Text(mid_char):set(string.rep(mid_char:content(), left_gap_width)))
|
||||
end
|
||||
|
||||
line:append(content)
|
||||
|
||||
if right_gap_width > 0 then
|
||||
line:append(Text(mid_char):set(string.rep(mid_char:content(), right_gap_width)))
|
||||
end
|
||||
|
||||
line:append(right_char)
|
||||
|
||||
return line
|
||||
end
|
||||
|
||||
---@param internal nui_popup_border_internal
|
||||
---@return nil|NuiLine[]
|
||||
local function calculate_buf_lines(internal)
|
||||
local char, size, text = internal.char, internal.size, internal.text or {}
|
||||
|
||||
if type(char) == "string" then
|
||||
return nil
|
||||
end
|
||||
|
||||
local left_char, right_char = char.left, char.right
|
||||
|
||||
local gap_length = size.width - left_char:width() - right_char:width()
|
||||
|
||||
---@type NuiLine[]
|
||||
local lines = {}
|
||||
|
||||
table.insert(lines, calculate_buf_edge_line(internal, "top", text.top, text.top_align))
|
||||
for _ = 1, size.height - 2 do
|
||||
table.insert(
|
||||
lines,
|
||||
Line({
|
||||
Text(left_char),
|
||||
Text(string.rep(" ", gap_length)),
|
||||
Text(right_char),
|
||||
})
|
||||
)
|
||||
end
|
||||
table.insert(lines, calculate_buf_edge_line(internal, "bottom", text.bottom, text.bottom_align))
|
||||
|
||||
return lines
|
||||
end
|
||||
|
||||
local styles = {
|
||||
bold = to_border_map({ "┏", "━", "┓", "┃", "┛", "━", "┗", "┃" }),
|
||||
double = to_border_map({ "╔", "═", "╗", "║", "╝", "═", "╚", "║" }),
|
||||
none = "none",
|
||||
rounded = to_border_map({ "╭", "─", "╮", "│", "╯", "─", "╰", "│" }),
|
||||
shadow = "shadow",
|
||||
single = to_border_map({ "┌", "─", "┐", "│", "┘", "─", "└", "│" }),
|
||||
solid = to_border_map({ " ", " ", " ", " ", " ", " ", " ", " " }),
|
||||
}
|
||||
|
||||
---@param style nui_popup_border_option_style
|
||||
---@param prev_char_map? _nui_popup_border_internal_char
|
||||
---@return _nui_popup_border_style_map
|
||||
local function prepare_char_map(style, prev_char_map)
|
||||
if type(style) == "string" then
|
||||
if not styles[style] then
|
||||
error("invalid border style name")
|
||||
end
|
||||
|
||||
---@cast style _nui_popup_border_style_builtin
|
||||
return vim.deepcopy(styles[style])
|
||||
end
|
||||
|
||||
if is_type("list", style) then
|
||||
---@cast style _nui_popup_border_style_list
|
||||
return to_border_map(style)
|
||||
end
|
||||
|
||||
---@cast style _nui_popup_border_style_map
|
||||
return vim.tbl_extend("force", prev_char_map or {}, style)
|
||||
end
|
||||
|
||||
---@param internal nui_popup_border_internal
|
||||
---@return nui_popup_border_internal_size
|
||||
local function calculate_size_delta(internal)
|
||||
---@type nui_popup_border_internal_size
|
||||
local delta = {
|
||||
width = 0,
|
||||
height = 0,
|
||||
}
|
||||
|
||||
local char = internal.char
|
||||
if type(char) == "table" then
|
||||
if not is_empty_char(char.top) then
|
||||
delta.height = delta.height + 1
|
||||
end
|
||||
|
||||
if not is_empty_char(char.bottom) then
|
||||
delta.height = delta.height + 1
|
||||
end
|
||||
|
||||
if not is_empty_char(char.left) then
|
||||
delta.width = delta.width + 1
|
||||
end
|
||||
|
||||
if not is_empty_char(char.right) then
|
||||
delta.width = delta.width + 1
|
||||
end
|
||||
end
|
||||
|
||||
local padding = internal.padding
|
||||
if padding then
|
||||
if padding.top then
|
||||
delta.height = delta.height + padding.top
|
||||
end
|
||||
|
||||
if padding.bottom then
|
||||
delta.height = delta.height + padding.bottom
|
||||
end
|
||||
|
||||
if padding.left then
|
||||
delta.width = delta.width + padding.left
|
||||
end
|
||||
|
||||
if padding.right then
|
||||
delta.width = delta.width + padding.right
|
||||
end
|
||||
end
|
||||
|
||||
return delta
|
||||
end
|
||||
|
||||
---@param border NuiPopupBorder
|
||||
---@return nui_popup_border_internal_size
|
||||
local function calculate_size(border)
|
||||
---@type nui_popup_border_internal_size
|
||||
local size = vim.deepcopy(border.popup._.size)
|
||||
|
||||
size.width = size.width + border._.size_delta.width
|
||||
size.height = size.height + border._.size_delta.height
|
||||
|
||||
return size
|
||||
end
|
||||
|
||||
---@param border NuiPopupBorder
|
||||
---@return nui_popup_border_internal_position
|
||||
local function calculate_position(border)
|
||||
local position = vim.deepcopy(border.popup._.position)
|
||||
position.col = position.col - math.floor(border._.size_delta.width / 2 + 0.5)
|
||||
position.row = position.row - math.floor(border._.size_delta.height / 2 + 0.5)
|
||||
return position
|
||||
end
|
||||
|
||||
local function adjust_popup_win_config(border)
|
||||
local internal = border._
|
||||
|
||||
local popup_position = {
|
||||
row = 0,
|
||||
col = 0,
|
||||
}
|
||||
|
||||
local char = internal.char
|
||||
|
||||
if type(char) == "table" then
|
||||
if not is_empty_char(char.top) then
|
||||
popup_position.row = popup_position.row + 1
|
||||
end
|
||||
|
||||
if not is_empty_char(char.left) then
|
||||
popup_position.col = popup_position.col + 1
|
||||
end
|
||||
end
|
||||
|
||||
local padding = internal.padding
|
||||
|
||||
if padding then
|
||||
if padding.top then
|
||||
popup_position.row = popup_position.row + padding.top
|
||||
end
|
||||
|
||||
if padding.left then
|
||||
popup_position.col = popup_position.col + padding.left
|
||||
end
|
||||
end
|
||||
|
||||
local popup = border.popup
|
||||
|
||||
-- luacov: disable
|
||||
if not has_nvim_0_5_1 then
|
||||
popup.win_config.row = internal.position.row + popup_position.row
|
||||
popup.win_config.col = internal.position.col + popup_position.col
|
||||
return
|
||||
end
|
||||
-- luacov: enable
|
||||
|
||||
-- relative to the border window
|
||||
popup.win_config.anchor = nil
|
||||
popup.win_config.relative = "win"
|
||||
popup.win_config.win = border.winid
|
||||
popup.win_config.bufpos = nil
|
||||
popup.win_config.row = popup_position.row
|
||||
popup.win_config.col = popup_position.col
|
||||
end
|
||||
|
||||
--luacheck: push no max line length
|
||||
|
||||
---@alias nui_t_text_align 'left'|'center'|'right'
|
||||
|
||||
---@alias nui_popup_border_internal_type 'simple'|'complex'
|
||||
---@alias nui_popup_border_internal_position table<'row'|'col', number>
|
||||
---@alias nui_popup_border_internal_size table<'height'|'width', number>
|
||||
---@alias nui_popup_border_internal_padding _nui_popup_border_option_padding_map
|
||||
---@alias nui_popup_border_internal_text { top?: NuiLine|NuiText, top_align?: nui_t_text_align, bottom?: NuiLine|NuiText, bottom_align?: nui_t_text_align }
|
||||
---@alias _nui_popup_border_internal_char table<_nui_popup_border_style_map_position, NuiText>
|
||||
|
||||
---@alias _nui_popup_border_option_padding_list table<1|2|3|4, integer>
|
||||
---@alias _nui_popup_border_option_padding_map table<'top'|'right'|'bottom'|'left', integer>
|
||||
---@alias nui_popup_border_option_padding _nui_popup_border_option_padding_list|_nui_popup_border_option_padding_map
|
||||
|
||||
---@alias _nui_popup_border_style_char_tuple table<1|2, string>
|
||||
---@alias _nui_popup_border_style_char string|_nui_popup_border_style_char_tuple|NuiText
|
||||
---@alias _nui_popup_border_style_builtin 'double'|'none'|'rounded'|'shadow'|'single'|'solid'|'default'
|
||||
---@alias _nui_popup_border_style_list table<1|2|3|4|5|6|7|8, _nui_popup_border_style_char>
|
||||
---@alias _nui_popup_border_style_map_position 'top_left'|'top'|'top_right'|'right'|'bottom_right'|'bottom'|'bottom_left'|'left'
|
||||
---@alias _nui_popup_border_style_map table<_nui_popup_border_style_map_position, _nui_popup_border_style_char>
|
||||
---@alias nui_popup_border_option_style _nui_popup_border_style_builtin|_nui_popup_border_style_list|_nui_popup_border_style_map
|
||||
|
||||
---@alias _nui_popup_border_option_text_value string|NuiLine|NuiText|string[]|table<1|2, string>[]
|
||||
---@alias nui_popup_border_option_text { top?: _nui_popup_border_option_text_value, top_align?: nui_t_text_align, bottom?: _nui_popup_border_option_text_value, bottom_align?: nui_t_text_align }
|
||||
|
||||
--luacheck: pop
|
||||
|
||||
---@class nui_popup_border_internal
|
||||
---@field type nui_popup_border_internal_type
|
||||
---@field style nui_popup_border_option_style
|
||||
---@field char _nui_popup_border_internal_char
|
||||
---@field padding? _nui_popup_border_option_padding_map
|
||||
---@field position nui_popup_border_internal_position
|
||||
---@field size nui_popup_border_internal_size
|
||||
---@field size_delta nui_popup_border_internal_size
|
||||
---@field text? nui_popup_border_internal_text
|
||||
---@field lines? NuiLine[]
|
||||
---@field winhighlight? string
|
||||
|
||||
---@class nui_popup_border_options
|
||||
---@field padding? nui_popup_border_option_padding
|
||||
---@field style? nui_popup_border_option_style
|
||||
---@field text? nui_popup_border_option_text
|
||||
|
||||
---@class NuiPopupBorder
|
||||
---@field bufnr integer
|
||||
---@field private _ nui_popup_border_internal
|
||||
---@field private popup NuiPopup
|
||||
---@field win_config nui_popup_win_config
|
||||
---@field winid number
|
||||
local Border = Object("NuiPopupBorder")
|
||||
|
||||
---@param popup NuiPopup
|
||||
---@param options nui_popup_border_options
|
||||
function Border:init(popup, options)
|
||||
self.popup = popup
|
||||
|
||||
self._ = {
|
||||
---@deprecated
|
||||
highlight = options.highlight,
|
||||
padding = normalize_option_padding(options.padding),
|
||||
text = normalize_option_text(options.text),
|
||||
}
|
||||
|
||||
local internal = self._
|
||||
|
||||
if internal.text or internal.padding then
|
||||
internal.type = "complex"
|
||||
else
|
||||
internal.type = "simple"
|
||||
end
|
||||
|
||||
self:set_style(options.style or _.get_default_winborder())
|
||||
|
||||
internal.winhighlight = calculate_winhighlight(internal, self.popup._.win_options.winhighlight)
|
||||
|
||||
if internal.type == "simple" then
|
||||
return self
|
||||
end
|
||||
|
||||
self:_buf_create()
|
||||
|
||||
self.win_config = {
|
||||
style = "minimal",
|
||||
border = "none",
|
||||
focusable = false,
|
||||
zindex = self.popup.win_config.zindex,
|
||||
anchor = self.popup.win_config.anchor,
|
||||
}
|
||||
|
||||
if type(internal.char) == "string" then
|
||||
self.win_config.border = internal.char
|
||||
end
|
||||
end
|
||||
|
||||
function Border:_open_window()
|
||||
if self.winid or not self.bufnr then
|
||||
return
|
||||
end
|
||||
|
||||
self.win_config.noautocmd = true
|
||||
self.winid = vim.api.nvim_open_win(self.bufnr, false, self.win_config)
|
||||
self.win_config.noautocmd = nil
|
||||
assert(self.winid, "failed to create border window")
|
||||
|
||||
if self._.winhighlight then
|
||||
_.set_win_option(self.winid, "winhighlight", self._.winhighlight)
|
||||
end
|
||||
|
||||
if self.popup._.win_options.winblend then
|
||||
_.set_win_option(self.winid, "winblend", self.popup._.win_options.winblend)
|
||||
end
|
||||
|
||||
adjust_popup_win_config(self)
|
||||
|
||||
vim.api.nvim_command("redraw")
|
||||
end
|
||||
|
||||
function Border:_close_window()
|
||||
if not self.winid then
|
||||
return
|
||||
end
|
||||
|
||||
if vim.api.nvim_win_is_valid(self.winid) then
|
||||
vim.api.nvim_win_close(self.winid, true)
|
||||
end
|
||||
|
||||
self.winid = nil
|
||||
end
|
||||
|
||||
function Border:_buf_create()
|
||||
if not self.bufnr or not vim.api.nvim_buf_is_valid(self.bufnr) then
|
||||
self.bufnr = vim.api.nvim_create_buf(false, true)
|
||||
vim.bo[self.bufnr].modifiable = true
|
||||
assert(self.bufnr, "failed to create border buffer")
|
||||
end
|
||||
end
|
||||
|
||||
function Border:mount()
|
||||
local popup = self.popup
|
||||
|
||||
if not popup._.loading or popup._.mounted then
|
||||
return
|
||||
end
|
||||
|
||||
local internal = self._
|
||||
|
||||
if internal.type == "simple" then
|
||||
return
|
||||
end
|
||||
|
||||
self:_buf_create()
|
||||
|
||||
if internal.lines then
|
||||
_.render_lines(internal.lines, self.bufnr, popup.ns_id, 1, #internal.lines)
|
||||
end
|
||||
|
||||
self:_open_window()
|
||||
end
|
||||
|
||||
function Border:unmount()
|
||||
local popup = self.popup
|
||||
|
||||
if not popup._.loading or not popup._.mounted then
|
||||
return
|
||||
end
|
||||
|
||||
local internal = self._
|
||||
|
||||
if internal.type == "simple" then
|
||||
return
|
||||
end
|
||||
|
||||
if self.bufnr then
|
||||
if vim.api.nvim_buf_is_valid(self.bufnr) then
|
||||
_.clear_namespace(self.bufnr, self.popup.ns_id)
|
||||
vim.api.nvim_buf_delete(self.bufnr, { force = true })
|
||||
end
|
||||
self.bufnr = nil
|
||||
end
|
||||
|
||||
self:_close_window()
|
||||
end
|
||||
|
||||
function Border:_relayout()
|
||||
local internal = self._
|
||||
|
||||
if internal.type ~= "complex" then
|
||||
return
|
||||
end
|
||||
|
||||
if self.popup.win_config.anchor and self.popup.win_config.anchor ~= self.win_config.anchor then
|
||||
self.win_config.anchor = self.popup.win_config.anchor
|
||||
self.popup.win_config.anchor = nil
|
||||
end
|
||||
|
||||
local position = self.popup._.position
|
||||
self.win_config.relative = position.relative
|
||||
self.win_config.win = position.relative == "win" and position.win or nil
|
||||
self.win_config.bufpos = position.bufpos
|
||||
|
||||
internal.size = calculate_size(self)
|
||||
self.win_config.width = internal.size.width
|
||||
self.win_config.height = internal.size.height
|
||||
|
||||
internal.position = calculate_position(self)
|
||||
self.win_config.row = internal.position.row
|
||||
self.win_config.col = internal.position.col
|
||||
|
||||
internal.lines = calculate_buf_lines(internal)
|
||||
|
||||
if self.winid then
|
||||
vim.api.nvim_win_set_config(self.winid, self.win_config)
|
||||
end
|
||||
|
||||
if self.bufnr then
|
||||
if internal.lines then
|
||||
_.render_lines(internal.lines, self.bufnr, self.popup.ns_id, 1, #internal.lines)
|
||||
end
|
||||
end
|
||||
|
||||
adjust_popup_win_config(self)
|
||||
|
||||
vim.api.nvim_command("redraw")
|
||||
end
|
||||
|
||||
---@param edge "'top'" | "'bottom'"
|
||||
---@param text? nil|string|NuiLine|NuiText
|
||||
---@param align? nil | "'left'" | "'center'" | "'right'"
|
||||
function Border:set_text(edge, text, align)
|
||||
local internal = self._
|
||||
|
||||
if not internal.text then
|
||||
return
|
||||
end
|
||||
|
||||
internal.text[edge] = normalize_border_text(text)
|
||||
internal.text[edge .. "_align"] = align or internal.text[edge .. "_align"]
|
||||
|
||||
if not internal.lines then
|
||||
return
|
||||
end
|
||||
|
||||
local line = calculate_buf_edge_line(
|
||||
internal,
|
||||
edge,
|
||||
internal.text[edge],
|
||||
internal.text[edge .. "_align"] --[[@as nui_t_text_align]]
|
||||
)
|
||||
|
||||
local linenr = edge == "top" and 1 or #internal.lines
|
||||
|
||||
internal.lines[linenr] = line
|
||||
line:render(self.bufnr, self.popup.ns_id, linenr)
|
||||
end
|
||||
|
||||
---@param highlight string highlight group
|
||||
function Border:set_highlight(highlight)
|
||||
local internal = self._
|
||||
|
||||
local winhighlight_data = _.parse_winhighlight(self.popup._.win_options.winhighlight)
|
||||
winhighlight_data["FloatBorder"] = highlight
|
||||
self.popup._.win_options.winhighlight = _.serialize_winhighlight(winhighlight_data)
|
||||
if self.popup.winid then
|
||||
_.set_win_option(self.popup.winid, "winhighlight", self.popup._.win_options.winhighlight)
|
||||
end
|
||||
|
||||
internal.winhighlight = calculate_winhighlight(internal, self.popup._.win_options.winhighlight)
|
||||
if self.winid then
|
||||
_.set_win_option(self.winid, "winhighlight", internal.winhighlight)
|
||||
end
|
||||
end
|
||||
|
||||
---@param style nui_popup_border_option_style
|
||||
function Border:set_style(style)
|
||||
local internal = self._
|
||||
|
||||
if style == "default" then
|
||||
style = _.get_default_winborder()
|
||||
if style == "none" and internal.type == "complex" then
|
||||
style = "single"
|
||||
end
|
||||
end
|
||||
|
||||
internal.style = style
|
||||
|
||||
local char = prepare_char_map(internal.style, internal.char)
|
||||
|
||||
local is_borderless = type(char) == "string"
|
||||
if is_borderless then
|
||||
if not internal.char then -- initial
|
||||
if internal.text then
|
||||
error("text not supported for style:" .. char)
|
||||
end
|
||||
elseif internal.type == "complex" then -- subsequent
|
||||
error("cannot change from previous style to " .. char)
|
||||
end
|
||||
end
|
||||
|
||||
internal.char = normalize_char_map(char)
|
||||
internal.size_delta = calculate_size_delta(internal)
|
||||
end
|
||||
|
||||
---@param char_map _nui_popup_border_internal_char
|
||||
---@return _nui_popup_border_style_char_tuple[]
|
||||
local function to_tuple_list(char_map)
|
||||
---@type _nui_popup_border_style_char_tuple[]
|
||||
local border = {}
|
||||
|
||||
for index, name in ipairs(index_name) do
|
||||
if not char_map[name] then
|
||||
error(string.format("missing named border: %s", name))
|
||||
end
|
||||
|
||||
local char = char_map[name]
|
||||
border[index] = { char:content(), char.extmark.hl_group }
|
||||
end
|
||||
|
||||
return border
|
||||
end
|
||||
|
||||
---@return nil|_nui_popup_border_style_builtin|_nui_popup_border_style_char_tuple[]
|
||||
function Border:get()
|
||||
local internal = self._
|
||||
|
||||
if internal.type ~= "simple" then
|
||||
if has_nvim_0_11_0 then
|
||||
return "none"
|
||||
end
|
||||
return nil
|
||||
end
|
||||
|
||||
if type(internal.char) == "string" then
|
||||
return internal.char
|
||||
end
|
||||
|
||||
return to_tuple_list(internal.char)
|
||||
end
|
||||
|
||||
---@alias NuiPopupBorder.constructor fun(popup: NuiPopup, options: nui_popup_border_options): NuiPopupBorder
|
||||
---@type NuiPopupBorder|NuiPopupBorder.constructor
|
||||
local NuiPopupBorder = Border
|
||||
|
||||
return NuiPopupBorder
|
||||
426
.config/nvim/pack/tree/start/nui.nvim/lua/nui/popup/init.lua
Normal file
426
.config/nvim/pack/tree/start/nui.nvim/lua/nui/popup/init.lua
Normal file
|
|
@ -0,0 +1,426 @@
|
|||
local Border = require("nui.popup.border")
|
||||
local Object = require("nui.object")
|
||||
local buf_storage = require("nui.utils.buf_storage")
|
||||
local autocmd = require("nui.utils.autocmd")
|
||||
local keymap = require("nui.utils.keymap")
|
||||
|
||||
local utils = require("nui.utils")
|
||||
local _ = utils._
|
||||
local defaults = utils.defaults
|
||||
local is_type = utils.is_type
|
||||
|
||||
local layout_utils = require("nui.layout.utils")
|
||||
local u = {
|
||||
clear_namespace = _.clear_namespace,
|
||||
get_next_id = _.get_next_id,
|
||||
size = layout_utils.size,
|
||||
position = layout_utils.position,
|
||||
update_layout_config = layout_utils.update_layout_config,
|
||||
}
|
||||
|
||||
-- luacov: disable
|
||||
-- @deprecated
|
||||
---@param opacity number
|
||||
---@deprecated
|
||||
local function calculate_winblend(opacity)
|
||||
assert(0 <= opacity, "opacity must be equal or greater than 0")
|
||||
assert(opacity <= 1, "opacity must be equal or lesser than 0")
|
||||
return 100 - (opacity * 100)
|
||||
end
|
||||
-- luacov: enable
|
||||
|
||||
local function merge_default_options(options)
|
||||
options.relative = defaults(options.relative, "win")
|
||||
|
||||
options.enter = defaults(options.enter, false)
|
||||
options.zindex = defaults(options.zindex, 50)
|
||||
|
||||
options.buf_options = defaults(options.buf_options, {})
|
||||
options.win_options = defaults(options.win_options, {})
|
||||
|
||||
options.border = defaults(options.border, _.get_default_winborder())
|
||||
|
||||
return options
|
||||
end
|
||||
|
||||
local function normalize_options(options)
|
||||
options = _.normalize_layout_options(options)
|
||||
|
||||
if is_type("string", options.border) then
|
||||
options.border = {
|
||||
style = options.border,
|
||||
}
|
||||
end
|
||||
|
||||
return options
|
||||
end
|
||||
|
||||
--luacheck: push no max line length
|
||||
|
||||
---@alias nui_popup_internal_position { relative: "'cursor'"|"'editor'"|"'win'", win: number, bufpos?: number[], row: number, col: number }
|
||||
---@alias nui_popup_internal_size { height: number, width: number }
|
||||
---@alias nui_popup_win_config { focusable: boolean, style: "'minimal'", zindex: number, relative: "'cursor'"|"'editor'"|"'win'", win?: number, bufpos?: number[], row: number, col: number, width: number, height: number, border?: string|table, anchor?: nui_layout_option_anchor }
|
||||
|
||||
--luacheck: pop
|
||||
|
||||
---@class nui_popup_internal
|
||||
---@field augroup table<'hide'|'unmount', string>
|
||||
---@field buf_options table<string, any>
|
||||
---@field layout table
|
||||
---@field layout_ready boolean
|
||||
---@field loading boolean
|
||||
---@field mounted boolean
|
||||
---@field position nui_popup_internal_position
|
||||
---@field size nui_popup_internal_size
|
||||
---@field unmanaged_bufnr? boolean
|
||||
---@field win_config nui_popup_win_config
|
||||
---@field win_enter boolean
|
||||
---@field win_options table<string, any>
|
||||
|
||||
---@class nui_popup_options
|
||||
---@field border? _nui_popup_border_style_builtin|nui_popup_border_options
|
||||
---@field ns_id? string|integer
|
||||
---@field anchor? nui_layout_option_anchor
|
||||
---@field relative? nui_layout_option_relative_type|nui_layout_option_relative
|
||||
---@field position? number|string|nui_layout_option_position
|
||||
---@field size? number|string|nui_layout_option_size
|
||||
---@field enter? boolean
|
||||
---@field focusable? boolean
|
||||
---@field zindex? integer
|
||||
---@field buf_options? table<string, any>
|
||||
---@field win_options? table<string, any>
|
||||
---@field bufnr? integer
|
||||
|
||||
---@class NuiPopup
|
||||
---@field border NuiPopupBorder
|
||||
---@field bufnr integer
|
||||
---@field ns_id integer
|
||||
---@field private _ nui_popup_internal
|
||||
---@field win_config nui_popup_win_config
|
||||
---@field winid number
|
||||
local Popup = Object("NuiPopup")
|
||||
|
||||
---@param options nui_popup_options
|
||||
function Popup:init(options)
|
||||
local id = u.get_next_id()
|
||||
|
||||
options = merge_default_options(options)
|
||||
options = normalize_options(options)
|
||||
|
||||
self._ = {
|
||||
id = id,
|
||||
buf_options = options.buf_options,
|
||||
layout = {},
|
||||
layout_ready = false,
|
||||
loading = false,
|
||||
mounted = false,
|
||||
win_enter = options.enter,
|
||||
win_options = options.win_options,
|
||||
win_config = {
|
||||
focusable = options.focusable,
|
||||
style = "minimal",
|
||||
anchor = options.anchor,
|
||||
zindex = options.zindex,
|
||||
},
|
||||
augroup = {
|
||||
hide = string.format("%s_hide", id),
|
||||
unmount = string.format("%s_unmount", id),
|
||||
},
|
||||
}
|
||||
|
||||
self.win_config = self._.win_config
|
||||
|
||||
self.ns_id = _.normalize_namespace_id(options.ns_id)
|
||||
|
||||
if options.bufnr then
|
||||
self.bufnr = options.bufnr
|
||||
self._.unmanaged_bufnr = true
|
||||
else
|
||||
self:_buf_create()
|
||||
end
|
||||
|
||||
-- luacov: disable
|
||||
-- @deprecated
|
||||
if not self._.win_options.winblend and is_type("number", options.opacity) then
|
||||
self._.win_options.winblend = calculate_winblend(options.opacity)
|
||||
end
|
||||
|
||||
-- @deprecated
|
||||
if not self._.win_options.winhighlight and not is_type("nil", options.highlight) then
|
||||
self._.win_options.winhighlight = options.highlight
|
||||
end
|
||||
-- luacov: enable
|
||||
|
||||
self.border = Border(self, options.border)
|
||||
self.win_config.border = self.border:get()
|
||||
|
||||
if options.position and options.size then
|
||||
self:update_layout(options)
|
||||
end
|
||||
end
|
||||
|
||||
function Popup:_open_window()
|
||||
if self.winid or not self.bufnr then
|
||||
return
|
||||
end
|
||||
|
||||
self.win_config.noautocmd = true
|
||||
self.winid = vim.api.nvim_open_win(self.bufnr, self._.win_enter, self.win_config)
|
||||
self.win_config.noautocmd = nil
|
||||
|
||||
vim.api.nvim_win_call(self.winid, function()
|
||||
autocmd.exec("BufWinEnter", {
|
||||
buffer = self.bufnr,
|
||||
modeline = false,
|
||||
})
|
||||
end)
|
||||
|
||||
assert(self.winid, "failed to create popup window")
|
||||
|
||||
_.set_win_options(self.winid, self._.win_options)
|
||||
end
|
||||
|
||||
function Popup:_close_window()
|
||||
if not self.winid then
|
||||
return
|
||||
end
|
||||
|
||||
if vim.api.nvim_win_is_valid(self.winid) then
|
||||
vim.api.nvim_win_close(self.winid, true)
|
||||
end
|
||||
|
||||
self.winid = nil
|
||||
end
|
||||
|
||||
function Popup:_buf_create()
|
||||
if not self.bufnr then
|
||||
self.bufnr = vim.api.nvim_create_buf(false, true)
|
||||
assert(self.bufnr, "failed to create buffer")
|
||||
end
|
||||
end
|
||||
|
||||
function Popup:mount()
|
||||
if not self._.layout_ready then
|
||||
return error("layout is not ready")
|
||||
end
|
||||
|
||||
if self._.loading or self._.mounted then
|
||||
return
|
||||
end
|
||||
|
||||
self._.loading = true
|
||||
|
||||
autocmd.create_group(self._.augroup.hide, { clear = true })
|
||||
autocmd.create_group(self._.augroup.unmount, { clear = true })
|
||||
autocmd.create("QuitPre", {
|
||||
group = self._.augroup.unmount,
|
||||
buffer = self.bufnr,
|
||||
callback = vim.schedule_wrap(function()
|
||||
self:unmount()
|
||||
end),
|
||||
}, self.bufnr)
|
||||
autocmd.create("BufWinEnter", {
|
||||
group = self._.augroup.unmount,
|
||||
buffer = self.bufnr,
|
||||
callback = function()
|
||||
-- When two popup using the same buffer and both of them
|
||||
-- are hidden, calling `:show` for one of them fires
|
||||
-- `BufWinEnter` for both of them. And in that scenario
|
||||
-- one of them will not have `self.winid`.
|
||||
if self.winid then
|
||||
-- @todo skip registering `WinClosed` multiple times
|
||||
-- for the same popup
|
||||
autocmd.create("WinClosed", {
|
||||
group = self._.augroup.hide,
|
||||
nested = true,
|
||||
pattern = tostring(self.winid),
|
||||
callback = function()
|
||||
self:hide()
|
||||
end,
|
||||
}, self.bufnr)
|
||||
end
|
||||
end,
|
||||
}, self.bufnr)
|
||||
|
||||
self.border:mount()
|
||||
|
||||
self:_buf_create()
|
||||
|
||||
_.set_buf_options(self.bufnr, self._.buf_options)
|
||||
|
||||
self:_open_window()
|
||||
|
||||
self._.loading = false
|
||||
self._.mounted = true
|
||||
end
|
||||
|
||||
function Popup:hide()
|
||||
if self._.loading or not self._.mounted then
|
||||
return
|
||||
end
|
||||
|
||||
self._.loading = true
|
||||
|
||||
pcall(autocmd.delete_group, self._.augroup.hide)
|
||||
|
||||
self.border:_close_window()
|
||||
|
||||
self:_close_window()
|
||||
|
||||
self._.loading = false
|
||||
end
|
||||
|
||||
function Popup:show()
|
||||
if self._.loading then
|
||||
return
|
||||
end
|
||||
|
||||
if not self._.mounted then
|
||||
return self:mount()
|
||||
end
|
||||
|
||||
self._.loading = true
|
||||
|
||||
autocmd.create_group(self._.augroup.hide, { clear = true })
|
||||
|
||||
self.border:_open_window()
|
||||
|
||||
self:_open_window()
|
||||
|
||||
self._.loading = false
|
||||
end
|
||||
|
||||
function Popup:_buf_destroy()
|
||||
if self.bufnr then
|
||||
if vim.api.nvim_buf_is_valid(self.bufnr) then
|
||||
u.clear_namespace(self.bufnr, self.ns_id)
|
||||
if not self._.unmanaged_bufnr then
|
||||
vim.api.nvim_buf_delete(self.bufnr, { force = true })
|
||||
end
|
||||
end
|
||||
|
||||
buf_storage.cleanup(self.bufnr)
|
||||
|
||||
if not self._.unmanaged_bufnr then
|
||||
self.bufnr = nil
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
function Popup:unmount()
|
||||
if self._.loading or not self._.mounted then
|
||||
return
|
||||
end
|
||||
|
||||
self._.loading = true
|
||||
|
||||
pcall(autocmd.delete_group, self._.augroup.hide)
|
||||
pcall(autocmd.delete_group, self._.augroup.unmount)
|
||||
|
||||
self.border:unmount()
|
||||
|
||||
self:_buf_destroy()
|
||||
|
||||
self:_close_window()
|
||||
|
||||
self._.loading = false
|
||||
self._.mounted = false
|
||||
end
|
||||
|
||||
-- set keymap for this popup window
|
||||
---@param mode string check `:h :map-modes`
|
||||
---@param key string|string[] key for the mapping
|
||||
---@param handler string | fun(): nil handler for the mapping
|
||||
---@param opts? table<"'expr'"|"'noremap'"|"'nowait'"|"'remap'"|"'script'"|"'silent'"|"'unique'", boolean>
|
||||
---@return nil
|
||||
function Popup:map(mode, key, handler, opts, ___force___)
|
||||
if not self.bufnr then
|
||||
error("popup buffer not found.")
|
||||
end
|
||||
|
||||
return keymap.set(self.bufnr, mode, key, handler, opts, ___force___)
|
||||
end
|
||||
|
||||
---@param mode string check `:h :map-modes`
|
||||
---@param key string|string[] key for the mapping
|
||||
---@return nil
|
||||
function Popup:unmap(mode, key, ___force___)
|
||||
if not self.bufnr then
|
||||
error("popup buffer not found.")
|
||||
end
|
||||
|
||||
return keymap._del(self.bufnr, mode, key, ___force___)
|
||||
end
|
||||
|
||||
---@param event string | string[]
|
||||
---@param handler string | function
|
||||
---@param options? table<"'once'" | "'nested'", boolean>
|
||||
function Popup:on(event, handler, options)
|
||||
if not self.bufnr then
|
||||
error("popup buffer not found.")
|
||||
end
|
||||
|
||||
autocmd.buf.define(self.bufnr, event, handler, options)
|
||||
end
|
||||
|
||||
---@param event? string | string[]
|
||||
function Popup:off(event)
|
||||
if not self.bufnr then
|
||||
error("popup buffer not found.")
|
||||
end
|
||||
|
||||
autocmd.buf.remove(self.bufnr, nil, event)
|
||||
end
|
||||
|
||||
-- luacov: disable
|
||||
-- @deprecated
|
||||
-- Use `popup:update_layout`.
|
||||
---@deprecated
|
||||
function Popup:set_layout(config)
|
||||
return self:update_layout(config)
|
||||
end
|
||||
-- luacov: enable
|
||||
|
||||
---@param config? nui_layout_options
|
||||
function Popup:update_layout(config)
|
||||
config = config or {}
|
||||
|
||||
u.update_layout_config(self._, config)
|
||||
|
||||
self.border:_relayout()
|
||||
|
||||
self._.layout_ready = true
|
||||
|
||||
if self.winid then
|
||||
-- upstream issue: https://github.com/neovim/neovim/issues/20370
|
||||
local win_config_style = self.win_config.style
|
||||
---@diagnostic disable-next-line: assign-type-mismatch
|
||||
self.win_config.style = ""
|
||||
vim.api.nvim_win_set_config(self.winid, self.win_config)
|
||||
self.win_config.style = win_config_style
|
||||
end
|
||||
end
|
||||
|
||||
-- luacov: disable
|
||||
-- @deprecated
|
||||
-- Use `popup:update_layout`.
|
||||
---@deprecated
|
||||
function Popup:set_size(size)
|
||||
self:update_layout({ size = size })
|
||||
end
|
||||
-- luacov: enable
|
||||
|
||||
-- luacov: disable
|
||||
-- @deprecated
|
||||
-- Use `popup:update_layout`.
|
||||
---@deprecated
|
||||
function Popup:set_position(position, relative)
|
||||
self:update_layout({ position = position, relative = relative })
|
||||
end
|
||||
-- luacov: enable
|
||||
|
||||
---@alias NuiPopup.constructor fun(options: nui_popup_options): NuiPopup
|
||||
---@type NuiPopup|NuiPopup.constructor
|
||||
local NuiPopup = Popup
|
||||
|
||||
return NuiPopup
|
||||
Loading…
Add table
Add a link
Reference in a new issue