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
114
.config/nvim/pack/tree/start/nui.nvim/lua/nui/input/README.md
Normal file
114
.config/nvim/pack/tree/start/nui.nvim/lua/nui/input/README.md
Normal file
|
|
@ -0,0 +1,114 @@
|
|||
# Input
|
||||
|
||||
Input is an abstraction layer on top of Popup.
|
||||
|
||||
It uses prompt buffer (check `:h prompt-buffer`) for its popup window.
|
||||
|
||||
```lua
|
||||
local Input = require("nui.input")
|
||||
local event = require("nui.utils.autocmd").event
|
||||
|
||||
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,
|
||||
})
|
||||
```
|
||||
|
||||
If you provide the `on_change` function, it'll be run everytime value changes.
|
||||
|
||||
Pressing `<CR>` runs the `on_submit` callback function and closes the window.
|
||||
Pressing `<C-c>` runs the `on_close` callback function and closes the window.
|
||||
|
||||
Of course, you can override the default keymaps and add more. For example:
|
||||
|
||||
```lua
|
||||
-- unmount input by pressing `<Esc>` in normal mode
|
||||
input:map("n", "<Esc>", function()
|
||||
input:unmount()
|
||||
end, { noremap = true })
|
||||
```
|
||||
|
||||
You can manipulate the associated buffer and window using the
|
||||
`input.bufnr` and `input.winid` properties.
|
||||
|
||||
**NOTE**: the first argument accepts options for `nui.popup` component.
|
||||
|
||||
## Options
|
||||
|
||||
### `prompt`
|
||||
|
||||
**Type:** `string` or `NuiText`
|
||||
|
||||
Prefix in the input.
|
||||
|
||||
### `default_value`
|
||||
|
||||
**Type:** `string`
|
||||
|
||||
Default value placed in the input on mount
|
||||
|
||||
### `on_close`
|
||||
|
||||
**Type:** `function`
|
||||
|
||||
_Signature:_ `on_close() -> nil`
|
||||
|
||||
Callback function, called when input is closed.
|
||||
|
||||
### `on_submit`
|
||||
|
||||
**Type:** `function`
|
||||
|
||||
_Signature:_ `on_submit(value: string) -> nil`
|
||||
|
||||
Callback function, called when input value is submitted.
|
||||
|
||||
### `on_change`
|
||||
|
||||
**Type:** `function`
|
||||
|
||||
_Signature:_ `on_change(value: string) -> nil`
|
||||
|
||||
Callback function, called when input value is changed.
|
||||
|
||||
### `disable_cursor_position_patch`
|
||||
|
||||
By default, `nui.input` will try to make sure the cursor on parent window is not
|
||||
moved after input is submitted/closed. If you want to disable this behavior
|
||||
for some reason, you can set `disable_cursor_position_patch` to `true`.
|
||||
|
||||
## Methods
|
||||
|
||||
Methods from `nui.popup` are also available for `nui.input`.
|
||||
|
||||
## Wiki Page
|
||||
|
||||
You can find additional documentation/examples/guides/tips-n-tricks in [nui.input wiki page](https://github.com/MunifTanjim/nui.nvim/wiki/nui.input).
|
||||
174
.config/nvim/pack/tree/start/nui.nvim/lua/nui/input/init.lua
Normal file
174
.config/nvim/pack/tree/start/nui.nvim/lua/nui/input/init.lua
Normal file
|
|
@ -0,0 +1,174 @@
|
|||
local Popup = require("nui.popup")
|
||||
local Text = require("nui.text")
|
||||
local defaults = require("nui.utils").defaults
|
||||
local is_type = require("nui.utils").is_type
|
||||
local event = require("nui.utils.autocmd").event
|
||||
|
||||
-- exiting insert mode places cursor one character backward,
|
||||
-- so patch the cursor position to one character forward
|
||||
-- when unmounting input.
|
||||
---@param target_cursor number[]
|
||||
---@param force? boolean
|
||||
local function patch_cursor_position(target_cursor, force)
|
||||
local cursor = vim.api.nvim_win_get_cursor(0)
|
||||
|
||||
if target_cursor[2] == cursor[2] and force then
|
||||
-- didn't exit insert mode yet, but it's gonna
|
||||
vim.api.nvim_win_set_cursor(0, { cursor[1], cursor[2] + 1 })
|
||||
elseif target_cursor[2] - 1 == cursor[2] then
|
||||
-- already exited insert mode
|
||||
vim.api.nvim_win_set_cursor(0, { cursor[1], cursor[2] + 1 })
|
||||
end
|
||||
end
|
||||
|
||||
---@class nui_input_options
|
||||
---@field prompt? string|NuiText
|
||||
---@field default_value? string
|
||||
---@field on_change? fun(value: string): nil
|
||||
---@field on_close? fun(): nil
|
||||
---@field on_submit? fun(value: string): nil
|
||||
|
||||
---@class nui_input_internal: nui_popup_internal
|
||||
---@field default_value string
|
||||
---@field prompt NuiText
|
||||
---@field disable_cursor_position_patch boolean
|
||||
---@field on_change? fun(value: string): nil
|
||||
---@field on_close fun(): nil
|
||||
---@field on_submit fun(value: string): nil
|
||||
---@field pending_submit_value? string
|
||||
|
||||
---@class NuiInput: NuiPopup
|
||||
---@field private _ nui_input_internal
|
||||
local Input = Popup:extend("NuiInput")
|
||||
|
||||
---@param popup_options nui_popup_options
|
||||
---@param options nui_input_options
|
||||
function Input:init(popup_options, options)
|
||||
popup_options.enter = false
|
||||
|
||||
popup_options.buf_options = defaults(popup_options.buf_options, {})
|
||||
popup_options.buf_options.buftype = "prompt"
|
||||
|
||||
if not is_type("table", popup_options.size) then
|
||||
popup_options.size = {
|
||||
width = popup_options.size,
|
||||
}
|
||||
end
|
||||
|
||||
popup_options.size.height = 1
|
||||
|
||||
Input.super.init(self, popup_options)
|
||||
|
||||
self._.default_value = defaults(options.default_value, "")
|
||||
self._.prompt = Text(defaults(options.prompt, ""))
|
||||
self._.disable_cursor_position_patch = defaults(options.disable_cursor_position_patch, false)
|
||||
|
||||
self.input_props = {}
|
||||
|
||||
self._.on_change = options.on_change
|
||||
self._.on_close = options.on_close or function() end
|
||||
self._.on_submit = options.on_submit or function() end
|
||||
end
|
||||
|
||||
function Input:mount()
|
||||
local props = self.input_props
|
||||
|
||||
if self._.mounted then
|
||||
return
|
||||
end
|
||||
|
||||
vim.fn.prompt_setprompt(self.bufnr, self._.prompt:content())
|
||||
|
||||
vim.api.nvim_buf_set_lines(self.bufnr, 0, -1, false, { self._.prompt:content() .. self._.default_value })
|
||||
|
||||
self:on(event.BufWinEnter, function()
|
||||
vim.schedule(function()
|
||||
if self._.prompt:length() > 0 then
|
||||
self._.prompt:highlight(self.bufnr, self.ns_id, 1, 0)
|
||||
end
|
||||
|
||||
vim.api.nvim_set_current_win(self.winid)
|
||||
end)
|
||||
|
||||
vim.api.nvim_command("startinsert!")
|
||||
end, { once = false })
|
||||
|
||||
Input.super.mount(self)
|
||||
|
||||
if self._.on_change then
|
||||
---@deprecated
|
||||
props.on_change = function()
|
||||
local value_with_prompt = vim.api.nvim_buf_get_lines(self.bufnr, 0, 1, false)[1]
|
||||
local value = string.sub(value_with_prompt, self._.prompt:length() + 1)
|
||||
self._.on_change(value)
|
||||
end
|
||||
|
||||
vim.api.nvim_buf_attach(self.bufnr, false, {
|
||||
on_lines = props.on_change,
|
||||
})
|
||||
end
|
||||
|
||||
---@deprecated
|
||||
props.on_submit = function(value)
|
||||
self._.pending_submit_value = value
|
||||
self:unmount()
|
||||
end
|
||||
|
||||
vim.fn.prompt_setcallback(self.bufnr, props.on_submit)
|
||||
|
||||
-- @deprecated
|
||||
--- Use `input:unmount`
|
||||
---@deprecated
|
||||
props.on_close = function()
|
||||
self:unmount()
|
||||
end
|
||||
|
||||
vim.fn.prompt_setinterrupt(self.bufnr, props.on_close)
|
||||
end
|
||||
|
||||
function Input:unmount()
|
||||
if not self._.mounted then
|
||||
return
|
||||
end
|
||||
|
||||
local container_winid = self._.container_info.winid
|
||||
local target_cursor = vim.api.nvim_win_is_valid(container_winid) and vim.api.nvim_win_get_cursor(container_winid)
|
||||
or nil
|
||||
local prompt_mode = vim.fn.mode()
|
||||
|
||||
Input.super.unmount(self)
|
||||
|
||||
if self._.loading then
|
||||
return
|
||||
end
|
||||
|
||||
self._.loading = true
|
||||
|
||||
local pending_submit_value = self._.pending_submit_value
|
||||
|
||||
vim.schedule(function()
|
||||
-- NOTE: on prompt-buffer normal mode <CR> causes neovim to enter insert mode.
|
||||
-- ref: https://github.com/neovim/neovim/blob/d8f5f4d09078/src/nvim/normal.c#L5327-L5333
|
||||
if (pending_submit_value and prompt_mode == "n") or prompt_mode == "i" then
|
||||
vim.api.nvim_command("stopinsert")
|
||||
end
|
||||
|
||||
if not self._.disable_cursor_position_patch and target_cursor ~= nil then
|
||||
patch_cursor_position(target_cursor, pending_submit_value and prompt_mode == "n")
|
||||
end
|
||||
|
||||
if pending_submit_value then
|
||||
self._.pending_submit_value = nil
|
||||
self._.on_submit(pending_submit_value)
|
||||
else
|
||||
self._.on_close()
|
||||
end
|
||||
self._.loading = false
|
||||
end)
|
||||
end
|
||||
|
||||
---@alias NuiInput.constructor fun(popup_options: nui_popup_options, options: nui_input_options): NuiInput
|
||||
---@type NuiInput|NuiInput.constructor
|
||||
local NuiInput = Input
|
||||
|
||||
return NuiInput
|
||||
307
.config/nvim/pack/tree/start/nui.nvim/lua/nui/layout/README.md
Normal file
307
.config/nvim/pack/tree/start/nui.nvim/lua/nui/layout/README.md
Normal file
|
|
@ -0,0 +1,307 @@
|
|||
# Layout
|
||||
|
||||
Layout is a helper component for creating complex layout by automatically
|
||||
handling the calculation for position and size of other components.
|
||||
|
||||
**Example**
|
||||
|
||||
```lua
|
||||
local Layout = require("nui.layout")
|
||||
local Popup = require("nui.popup")
|
||||
|
||||
local top_popup = Popup({ border = "double" })
|
||||
local bottom_left_popup = Popup({ border = "single" })
|
||||
local bottom_right_popup = Popup({ border = "single" })
|
||||
|
||||
local layout = Layout(
|
||||
{
|
||||
position = "50%",
|
||||
size = {
|
||||
width = 80,
|
||||
height = 40,
|
||||
},
|
||||
},
|
||||
Layout.Box({
|
||||
Layout.Box(top_popup, { size = "40%" }),
|
||||
Layout.Box({
|
||||
Layout.Box(bottom_left_popup, { size = "50%" }),
|
||||
Layout.Box(bottom_right_popup, { size = "50%" }),
|
||||
}, { dir = "row", size = "60%" }),
|
||||
}, { dir = "col" })
|
||||
)
|
||||
|
||||
layout:mount()
|
||||
```
|
||||
|
||||
_Signature:_ `Layout(options, box)` or `Layout(component, box)`
|
||||
|
||||
`component` can be `Popup` or `Split`.
|
||||
|
||||
## Options (for float layout)
|
||||
|
||||
### `anchor`
|
||||
|
||||
**Type:** `"NW"` / `"NE"` / `"SW"` / `"SE"`
|
||||
|
||||
Decides which corner of the layout 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 layout.
|
||||
|
||||
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,
|
||||
},
|
||||
```
|
||||
|
||||
## Options (for split layout)
|
||||
|
||||
### `relative`
|
||||
|
||||
**Type:** `string` or `table`
|
||||
|
||||
This option affects how `size` is calculated.
|
||||
|
||||
**Examples**
|
||||
|
||||
Split current editor screen:
|
||||
|
||||
```lua
|
||||
relative = "editor"
|
||||
```
|
||||
|
||||
Split current window (_default_):
|
||||
|
||||
```lua
|
||||
relative = "win"
|
||||
```
|
||||
|
||||
Split window with specific id:
|
||||
|
||||
```lua
|
||||
relative = {
|
||||
type = "win",
|
||||
winid = 42,
|
||||
}
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
### `position`
|
||||
|
||||
**Type:** `"top" | "right"| "bottom" | "left"`.
|
||||
|
||||
---
|
||||
|
||||
### `size`
|
||||
|
||||
**Type:** `number` or `percentage string`
|
||||
|
||||
Determines the size of the layout.
|
||||
|
||||
For `percentage string`, size is calculated according to the option `relative`.
|
||||
|
||||
## Layout.Box
|
||||
|
||||
_Signature:_ `Layout.Box(box, options)`
|
||||
|
||||
**Parameters**
|
||||
|
||||
| Name | Type | Description |
|
||||
| --------- | ------------------------------ | ----------------------------------------- |
|
||||
| `box` | `Layout.Box[]` / nui component | list of `Layout.Box` or any nui component |
|
||||
| `options` | `table` | box options |
|
||||
|
||||
`options` is a `table` having the following keys:
|
||||
|
||||
| Key | Type | Description |
|
||||
| ------ | ----------------------------- | ------------------------------------------------------ |
|
||||
| `dir` | `"col"` / `"row"` (_default_) | arrangement direction, only if `box` is `Layout.Box[]` |
|
||||
| `grow` | `number` | growth factor to fill up the box free space |
|
||||
| `size` | `number` / `string` / `table` | optional if `grow` is present |
|
||||
|
||||
## Methods
|
||||
|
||||
### `layout:mount`
|
||||
|
||||
_Signature:_ `layout:mount()`
|
||||
|
||||
Mounts the layout with all the components.
|
||||
|
||||
**Examples**
|
||||
|
||||
```lua
|
||||
layout:mount()
|
||||
```
|
||||
|
||||
### `layout:unmount`
|
||||
|
||||
_Signature:_ `layout:unmount()`
|
||||
|
||||
Unmounts the layout with all the components.
|
||||
|
||||
**Examples**
|
||||
|
||||
```lua
|
||||
layout:unmount()
|
||||
```
|
||||
|
||||
### `layout:hide`
|
||||
|
||||
_Signature:_ `layout:hide()`
|
||||
|
||||
Hides the layout with all the components. Preserves the buffer (related content, autocmds and keymaps).
|
||||
|
||||
### `layout:show`
|
||||
|
||||
_Signature:_ `layout:show()`
|
||||
|
||||
Shows the hidden layout with all the components.
|
||||
|
||||
### `layout:update`
|
||||
|
||||
_Signature:_ `layout:update(config, box?)` or `layout:update(box?)`
|
||||
|
||||
**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` |
|
||||
|
||||
`box` is a `table` returned by `Layout.Box`.
|
||||
|
||||
They are the same options used for layout initialization.
|
||||
|
||||
## Wiki Page
|
||||
|
||||
You can find additional documentation/examples/guides/tips-n-tricks in
|
||||
[nui.layout wiki page](https://github.com/MunifTanjim/nui.nvim/wiki/nui.layout).
|
||||
239
.config/nvim/pack/tree/start/nui.nvim/lua/nui/layout/float.lua
Normal file
239
.config/nvim/pack/tree/start/nui.nvim/lua/nui/layout/float.lua
Normal file
|
|
@ -0,0 +1,239 @@
|
|||
local utils = require("nui.utils")
|
||||
local layout_utils = require("nui.layout.utils")
|
||||
|
||||
local u = {
|
||||
is_type = utils.is_type,
|
||||
calculate_window_size = layout_utils.calculate_window_size,
|
||||
}
|
||||
|
||||
local mod = {}
|
||||
|
||||
local function get_child_position(box, child, current_position, canvas_position)
|
||||
local position = box.dir == "row" and {
|
||||
row = canvas_position.row,
|
||||
col = current_position.col,
|
||||
} or {
|
||||
col = canvas_position.col,
|
||||
row = current_position.row,
|
||||
}
|
||||
|
||||
if child.component then
|
||||
local border = child.component.border
|
||||
if border and border._.type == "complex" then
|
||||
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)
|
||||
end
|
||||
end
|
||||
|
||||
return position
|
||||
end
|
||||
|
||||
---@param parent table Layout.Box
|
||||
---@param child table Layout.Box
|
||||
---@param container_size table
|
||||
---@param growable_dimension_per_factor? number
|
||||
local function get_child_size(parent, child, container_size, growable_dimension_per_factor)
|
||||
local child_size = {
|
||||
width = child.size.width,
|
||||
height = child.size.height,
|
||||
}
|
||||
|
||||
if child.grow and growable_dimension_per_factor then
|
||||
if parent.dir == "col" then
|
||||
child_size.height = math.floor(growable_dimension_per_factor * child.grow)
|
||||
else
|
||||
child_size.width = math.floor(growable_dimension_per_factor * child.grow)
|
||||
end
|
||||
end
|
||||
|
||||
local outer_size = u.calculate_window_size(child_size, container_size)
|
||||
|
||||
local inner_size = {
|
||||
width = outer_size.width,
|
||||
height = outer_size.height,
|
||||
}
|
||||
|
||||
if child.component then
|
||||
if child.component.border then
|
||||
inner_size.width = inner_size.width - child.component.border._.size_delta.width
|
||||
inner_size.height = inner_size.height - child.component.border._.size_delta.height
|
||||
|
||||
if inner_size.height <= 0 then
|
||||
local height_adjustment = math.abs(inner_size.height) + 1
|
||||
inner_size.height = inner_size.height + height_adjustment
|
||||
outer_size.height = outer_size.height + height_adjustment
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
return outer_size, inner_size
|
||||
end
|
||||
|
||||
function mod.process(box, meta)
|
||||
-- luacov: disable
|
||||
if box.mount or box.component or not box.box then
|
||||
return error("invalid parameter: box")
|
||||
end
|
||||
-- luacov: enable
|
||||
|
||||
local container_size = meta.container_size
|
||||
|
||||
-- luacov: disable
|
||||
if not u.is_type("number", container_size.width) or not u.is_type("number", container_size.height) then
|
||||
return error("invalid value: box.size")
|
||||
end
|
||||
-- luacov: enable
|
||||
|
||||
local current_position = box.dir == "row" and {
|
||||
col = meta.position.col,
|
||||
row = 0,
|
||||
} or {
|
||||
col = 0,
|
||||
row = meta.position.row,
|
||||
}
|
||||
|
||||
local growable_child_factor = 0
|
||||
|
||||
for _, child in ipairs(box.box) do
|
||||
if meta.process_growable_child or not child.grow then
|
||||
local position = get_child_position(box, child, current_position, meta.position)
|
||||
local outer_size, inner_size = get_child_size(box, child, container_size, meta.growable_dimension_per_factor)
|
||||
|
||||
if child.component then
|
||||
child.component:set_layout({
|
||||
size = inner_size,
|
||||
relative = {
|
||||
type = "win",
|
||||
winid = meta.winid,
|
||||
},
|
||||
position = position,
|
||||
})
|
||||
else
|
||||
mod.process(child, {
|
||||
winid = meta.winid,
|
||||
container_size = outer_size,
|
||||
position = position,
|
||||
})
|
||||
end
|
||||
|
||||
current_position.col = current_position.col + outer_size.width
|
||||
current_position.row = current_position.row + outer_size.height
|
||||
end
|
||||
|
||||
if child.grow then
|
||||
growable_child_factor = growable_child_factor + child.grow
|
||||
end
|
||||
end
|
||||
|
||||
if meta.process_growable_child or growable_child_factor == 0 then
|
||||
return
|
||||
end
|
||||
|
||||
local growable_width = container_size.width - current_position.col
|
||||
local growable_height = container_size.height - current_position.row
|
||||
local growable_dimension = box.dir == "col" and growable_height or growable_width
|
||||
local growable_dimension_per_factor = growable_dimension / growable_child_factor
|
||||
|
||||
mod.process(box, {
|
||||
winid = meta.winid,
|
||||
container_size = meta.container_size,
|
||||
position = meta.position,
|
||||
process_growable_child = true,
|
||||
growable_dimension_per_factor = growable_dimension_per_factor,
|
||||
})
|
||||
end
|
||||
|
||||
function mod.mount_box(box)
|
||||
for _, child in ipairs(box.box) do
|
||||
if child.component then
|
||||
child.component:mount()
|
||||
else
|
||||
mod.mount_box(child)
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
---@param box table Layout.Box
|
||||
function mod.show_box(box)
|
||||
for _, child in ipairs(box.box) do
|
||||
if child.component then
|
||||
child.component:show()
|
||||
else
|
||||
mod.show_box(child)
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
function mod.unmount_box(box)
|
||||
for _, child in ipairs(box.box) do
|
||||
if child.component then
|
||||
child.component:unmount()
|
||||
else
|
||||
mod.unmount_box(child)
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
---@param box table Layout.Box
|
||||
function mod.hide_box(box)
|
||||
for _, child in ipairs(box.box) do
|
||||
if child.component then
|
||||
child.component:hide()
|
||||
else
|
||||
mod.hide_box(child)
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
---@param box table Layout.Box
|
||||
---@return table<string, table>
|
||||
local function collect_box_components(box, components)
|
||||
if not components then
|
||||
components = {}
|
||||
end
|
||||
|
||||
for _, child in ipairs(box.box) do
|
||||
if child.component then
|
||||
components[child.component._.id] = child.component
|
||||
else
|
||||
collect_box_components(child, components)
|
||||
end
|
||||
end
|
||||
|
||||
return components
|
||||
end
|
||||
|
||||
---@param curr_box table Layout.Box
|
||||
---@param prev_box table Layout.Box
|
||||
function mod.process_box_change(curr_box, prev_box)
|
||||
if curr_box == prev_box then
|
||||
return
|
||||
end
|
||||
|
||||
local curr_components = collect_box_components(curr_box)
|
||||
local prev_components = collect_box_components(prev_box)
|
||||
|
||||
for id, component in pairs(curr_components) do
|
||||
if not prev_components[id] then
|
||||
if not component.winid then
|
||||
if component._.mounted then
|
||||
component:show()
|
||||
else
|
||||
component:mount()
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
for id, component in pairs(prev_components) do
|
||||
if not curr_components[id] then
|
||||
if component._.mounted then
|
||||
if component.winid then
|
||||
component:hide()
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
return mod
|
||||
573
.config/nvim/pack/tree/start/nui.nvim/lua/nui/layout/init.lua
Normal file
573
.config/nvim/pack/tree/start/nui.nvim/lua/nui/layout/init.lua
Normal file
|
|
@ -0,0 +1,573 @@
|
|||
local Object = require("nui.object")
|
||||
local Popup = require("nui.popup")
|
||||
local Split = require("nui.split")
|
||||
local utils = require("nui.utils")
|
||||
local layout_utils = require("nui.layout.utils")
|
||||
local float_layout = require("nui.layout.float")
|
||||
local split_layout = require("nui.layout.split")
|
||||
local split_utils = require("nui.split.utils")
|
||||
local autocmd = require("nui.utils.autocmd")
|
||||
|
||||
local _ = utils._
|
||||
|
||||
local defaults = utils.defaults
|
||||
local is_type = utils.is_type
|
||||
local u = {
|
||||
get_next_id = _.get_next_id,
|
||||
position = layout_utils.position,
|
||||
size = layout_utils.size,
|
||||
split = split_utils,
|
||||
update_layout_config = layout_utils.update_layout_config,
|
||||
}
|
||||
|
||||
-- GitHub Issue: https://github.com/neovim/neovim/issues/18925
|
||||
local function apply_workaround_for_float_relative_position_issue_18925(layout)
|
||||
local winids_len = 1
|
||||
local winids = { layout.winid }
|
||||
local function collect_anchor_winids(box)
|
||||
for _, child in ipairs(box.box) do
|
||||
if child.component then
|
||||
local border = child.component.border
|
||||
if border and border.winid then
|
||||
winids_len = winids_len + 1
|
||||
winids[winids_len] = border.winid
|
||||
end
|
||||
else
|
||||
collect_anchor_winids(child)
|
||||
end
|
||||
end
|
||||
end
|
||||
collect_anchor_winids(layout._.box)
|
||||
|
||||
vim.schedule(function()
|
||||
-- check in case layout was immediately hidden or unmounted
|
||||
if layout.winid == winids[1] and vim.api.nvim_win_is_valid(winids[1]) then
|
||||
vim.cmd(
|
||||
("noa call nvim_set_current_win(%s)\nnormal! jk\nredraw\n"):rep(winids_len):format(unpack(winids))
|
||||
.. ("noa call nvim_set_current_win(%s)"):format(vim.api.nvim_get_current_win())
|
||||
)
|
||||
end
|
||||
end)
|
||||
end
|
||||
|
||||
---@param options nui_layout_options
|
||||
local function merge_default_options(options)
|
||||
options.relative = defaults(options.relative, "win")
|
||||
|
||||
return options
|
||||
end
|
||||
|
||||
---@param options nui_layout_options
|
||||
local function normalize_options(options)
|
||||
options = _.normalize_layout_options(options)
|
||||
|
||||
return options
|
||||
end
|
||||
|
||||
---@return boolean
|
||||
local function is_box(object)
|
||||
return object and (object.box or object.component)
|
||||
end
|
||||
|
||||
---@return boolean
|
||||
local function is_component(object)
|
||||
return object and object.mount
|
||||
end
|
||||
|
||||
local function is_component_mounted(component)
|
||||
return is_type("number", component.winid)
|
||||
end
|
||||
|
||||
---@param component NuiPopup|NuiSplit
|
||||
local function get_layout_config_relative_to_component(component)
|
||||
return {
|
||||
relative = { type = "win", winid = component.winid },
|
||||
position = { row = 0, col = 0 },
|
||||
size = { width = "100%", height = "100%" },
|
||||
}
|
||||
end
|
||||
|
||||
---@param layout NuiLayout
|
||||
---@param box table Layout.Box
|
||||
local function wire_up_layout_components(layout, box)
|
||||
for _, child in ipairs(box.box) do
|
||||
if child.component then
|
||||
autocmd.create({ "BufWipeout", "QuitPre" }, {
|
||||
group = layout._.augroup.unmount,
|
||||
buffer = child.component.bufnr,
|
||||
callback = vim.schedule_wrap(function()
|
||||
layout:unmount()
|
||||
end),
|
||||
}, child.component.bufnr)
|
||||
|
||||
autocmd.create("BufWinEnter", {
|
||||
group = layout._.augroup.unmount,
|
||||
buffer = child.component.bufnr,
|
||||
callback = function()
|
||||
local winid = child.component.winid
|
||||
if layout._.type == "float" and not winid then
|
||||
--[[
|
||||
`BufWinEnter` does not contain window id and
|
||||
it is fired before `nvim_open_win` returns
|
||||
the window id.
|
||||
--]]
|
||||
winid = vim.fn.bufwinid(child.component.bufnr)
|
||||
end
|
||||
|
||||
autocmd.create("WinClosed", {
|
||||
group = layout._.augroup.hide,
|
||||
nested = true,
|
||||
pattern = tostring(winid),
|
||||
callback = function()
|
||||
layout:hide()
|
||||
end,
|
||||
}, child.component.bufnr)
|
||||
end,
|
||||
}, child.component.bufnr)
|
||||
else
|
||||
wire_up_layout_components(layout, child)
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
---@class nui_layout_options
|
||||
---@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
|
||||
|
||||
---@class NuiLayout
|
||||
local Layout = Object("NuiLayout")
|
||||
|
||||
---@return '"float"'|'"split"' layout_type
|
||||
local function get_layout_type(box)
|
||||
for _, child in ipairs(box.box) do
|
||||
if child.component and child.type then
|
||||
return child.type
|
||||
end
|
||||
|
||||
local type = get_layout_type(child)
|
||||
if type then
|
||||
return type
|
||||
end
|
||||
end
|
||||
|
||||
error("unexpected empty box")
|
||||
end
|
||||
|
||||
---@param options nui_layout_options|NuiPopup|NuiSplit
|
||||
---@param box NuiLayout.Box|NuiLayout.Box[]
|
||||
function Layout:init(options, box)
|
||||
local id = u.get_next_id()
|
||||
|
||||
box = Layout.Box(box)
|
||||
|
||||
local type = get_layout_type(box)
|
||||
|
||||
self._ = {
|
||||
id = id,
|
||||
type = type,
|
||||
box = box,
|
||||
loading = false,
|
||||
mounted = false,
|
||||
augroup = {
|
||||
hide = string.format("%s_hide", id),
|
||||
unmount = string.format("%s_unmount", id),
|
||||
},
|
||||
}
|
||||
|
||||
if type == "float" then
|
||||
local container
|
||||
if is_component(options) then
|
||||
container = options --[[@as NuiPopup|NuiSplit]]
|
||||
options = get_layout_config_relative_to_component(container)
|
||||
else
|
||||
---@cast options -NuiPopup, -NuiSplit
|
||||
options = merge_default_options(options)
|
||||
options = normalize_options(options)
|
||||
end
|
||||
|
||||
self._[type] = {
|
||||
container = container,
|
||||
layout = {},
|
||||
win_enter = false,
|
||||
win_config = {
|
||||
border = "none",
|
||||
focusable = false,
|
||||
style = "minimal",
|
||||
anchor = options.anchor,
|
||||
zindex = 49,
|
||||
},
|
||||
win_options = {
|
||||
winblend = 100,
|
||||
},
|
||||
}
|
||||
|
||||
if not is_component(container) or is_component_mounted(container) then
|
||||
self:update(options)
|
||||
end
|
||||
end
|
||||
|
||||
if type == "split" then
|
||||
options = u.split.merge_default_options(options)
|
||||
options = u.split.normalize_options(options)
|
||||
|
||||
self._[type] = {
|
||||
layout = {},
|
||||
position = options.position,
|
||||
size = {},
|
||||
win_config = {
|
||||
pending_changes = {},
|
||||
},
|
||||
}
|
||||
|
||||
self:update(options)
|
||||
end
|
||||
end
|
||||
|
||||
function Layout:_process_layout()
|
||||
local type = self._.type
|
||||
|
||||
if type == "float" then
|
||||
local info = self._.float
|
||||
|
||||
float_layout.process(self._.box, {
|
||||
winid = self.winid,
|
||||
container_size = info.size,
|
||||
position = {
|
||||
row = 0,
|
||||
col = 0,
|
||||
},
|
||||
})
|
||||
|
||||
return
|
||||
end
|
||||
|
||||
if type == "split" then
|
||||
local info = self._.split
|
||||
|
||||
split_layout.process(self._.box, {
|
||||
position = info.position,
|
||||
relative = info.relative,
|
||||
container_size = info.size,
|
||||
container_fallback_size = info.container_info.size,
|
||||
})
|
||||
end
|
||||
end
|
||||
|
||||
function Layout:_open_window()
|
||||
if self._.type == "float" then
|
||||
local info = self._.float
|
||||
|
||||
self.winid = vim.api.nvim_open_win(self.bufnr, info.win_enter, info.win_config)
|
||||
assert(self.winid, "failed to create popup window")
|
||||
|
||||
_.set_win_options(self.winid, info.win_options)
|
||||
end
|
||||
end
|
||||
|
||||
function Layout:_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 Layout:mount()
|
||||
if self._.loading or self._.mounted then
|
||||
return
|
||||
end
|
||||
|
||||
self._.loading = true
|
||||
|
||||
local type = self._.type
|
||||
|
||||
if type == "float" then
|
||||
local info = self._.float
|
||||
|
||||
local container = info.container
|
||||
if is_component(container) and not is_component_mounted(container) then
|
||||
container:mount()
|
||||
self:update(get_layout_config_relative_to_component(container))
|
||||
end
|
||||
|
||||
if not self.bufnr then
|
||||
self.bufnr = vim.api.nvim_create_buf(false, true)
|
||||
assert(self.bufnr, "failed to create buffer")
|
||||
end
|
||||
|
||||
self:_open_window()
|
||||
end
|
||||
|
||||
self:_process_layout()
|
||||
|
||||
if type == "float" then
|
||||
float_layout.mount_box(self._.box)
|
||||
|
||||
apply_workaround_for_float_relative_position_issue_18925(self)
|
||||
end
|
||||
|
||||
if type == "split" then
|
||||
split_layout.mount_box(self._.box)
|
||||
end
|
||||
|
||||
self._.loading = false
|
||||
self._.mounted = true
|
||||
end
|
||||
|
||||
function Layout:unmount()
|
||||
if self._.loading or not self._.mounted then
|
||||
return
|
||||
end
|
||||
|
||||
pcall(autocmd.delete_group, self._.augroup.hide)
|
||||
pcall(autocmd.delete_group, self._.augroup.unmount)
|
||||
|
||||
self._.loading = true
|
||||
|
||||
local type = self._.type
|
||||
|
||||
if type == "float" then
|
||||
float_layout.unmount_box(self._.box)
|
||||
|
||||
if self.bufnr then
|
||||
if vim.api.nvim_buf_is_valid(self.bufnr) then
|
||||
vim.api.nvim_buf_delete(self.bufnr, { force = true })
|
||||
end
|
||||
self.bufnr = nil
|
||||
end
|
||||
|
||||
self:_close_window()
|
||||
end
|
||||
|
||||
if type == "split" then
|
||||
split_layout.unmount_box(self._.box)
|
||||
end
|
||||
|
||||
self._.loading = false
|
||||
self._.mounted = false
|
||||
end
|
||||
|
||||
function Layout:hide()
|
||||
if self._.loading or not self._.mounted then
|
||||
return
|
||||
end
|
||||
|
||||
self._.loading = true
|
||||
|
||||
pcall(autocmd.delete_group, self._.augroup.hide)
|
||||
|
||||
local type = self._.type
|
||||
|
||||
if type == "float" then
|
||||
float_layout.hide_box(self._.box)
|
||||
|
||||
self:_close_window()
|
||||
end
|
||||
|
||||
if type == "split" then
|
||||
split_layout.hide_box(self._.box)
|
||||
end
|
||||
|
||||
self._.loading = false
|
||||
end
|
||||
|
||||
function Layout: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 })
|
||||
|
||||
local type = self._.type
|
||||
|
||||
if type == "float" then
|
||||
self:_open_window()
|
||||
end
|
||||
|
||||
self:_process_layout()
|
||||
|
||||
if type == "float" then
|
||||
float_layout.show_box(self._.box)
|
||||
|
||||
apply_workaround_for_float_relative_position_issue_18925(self)
|
||||
end
|
||||
|
||||
if type == "split" then
|
||||
split_layout.show_box(self._.box)
|
||||
end
|
||||
|
||||
self._.loading = false
|
||||
end
|
||||
|
||||
---@param config? NuiLayout.Box|NuiLayout.Box[]|nui_layout_options
|
||||
---@param box? NuiLayout.Box
|
||||
function Layout:update(config, box)
|
||||
config = config or {} --[[@as nui_layout_options]]
|
||||
|
||||
if not box and is_box(config) or is_box(config[1]) then
|
||||
box = config --[=[@as NuiLayout.Box|NuiLayout.Box[]]=]
|
||||
---@type nui_layout_options
|
||||
config = {}
|
||||
end
|
||||
|
||||
autocmd.create_group(self._.augroup.hide, { clear = true })
|
||||
autocmd.create_group(self._.augroup.unmount, { clear = true })
|
||||
|
||||
local prev_box = self._.box
|
||||
|
||||
if box then
|
||||
self._.box = Layout.Box(box)
|
||||
self._.type = get_layout_type(self._.box)
|
||||
end
|
||||
|
||||
if self._.type == "float" then
|
||||
local info = self._.float
|
||||
|
||||
u.update_layout_config(info, config)
|
||||
|
||||
if self.winid then
|
||||
vim.api.nvim_win_set_config(self.winid, info.win_config)
|
||||
|
||||
self:_process_layout()
|
||||
|
||||
float_layout.process_box_change(self._.box, prev_box)
|
||||
|
||||
apply_workaround_for_float_relative_position_issue_18925(self)
|
||||
end
|
||||
|
||||
wire_up_layout_components(self, self._.box)
|
||||
end
|
||||
|
||||
if self._.type == "split" then
|
||||
local info = self._.split
|
||||
|
||||
local relative_winid = info.relative and info.relative.win
|
||||
|
||||
local prev_winid = vim.api.nvim_get_current_win()
|
||||
if relative_winid then
|
||||
vim.api.nvim_set_current_win(relative_winid)
|
||||
end
|
||||
|
||||
local curr_box = self._.box
|
||||
if prev_box ~= curr_box then
|
||||
self._.box = prev_box
|
||||
self:hide()
|
||||
self._.box = curr_box
|
||||
end
|
||||
|
||||
u.split.update_layout_config(info, config)
|
||||
|
||||
if prev_box == curr_box then
|
||||
self:_process_layout()
|
||||
else
|
||||
self:show()
|
||||
end
|
||||
|
||||
if vim.api.nvim_win_is_valid(prev_winid) then
|
||||
vim.api.nvim_set_current_win(prev_winid)
|
||||
end
|
||||
|
||||
wire_up_layout_components(self, self._.box)
|
||||
end
|
||||
end
|
||||
|
||||
---@class nui_layout_box_options
|
||||
---@field dir? 'row'|'col'
|
||||
---@field grow? integer
|
||||
---@field size? number|string|table<'height'|'width', number|string>
|
||||
|
||||
---@class NuiLayout.Box
|
||||
---@field type? 'float'|'split'
|
||||
---@field component? NuiPopup|NuiSplit
|
||||
---@field box? NuiLayout.Box[]
|
||||
---@field grow? integer
|
||||
---@field size? nui_layout_option_size
|
||||
|
||||
---@param box NuiPopup|NuiSplit|NuiLayout.Box|NuiLayout.Box[]
|
||||
---@param options? nui_layout_box_options
|
||||
---@return NuiLayout.Box
|
||||
function Layout.Box(box, options)
|
||||
options = options or {}
|
||||
|
||||
if is_box(box) then
|
||||
return box --[[@as NuiLayout.Box]]
|
||||
end
|
||||
|
||||
if box.mount then
|
||||
local type
|
||||
---@diagnostic disable: undefined-field
|
||||
if box:is_instance_of(Popup) then
|
||||
type = "float"
|
||||
elseif box:is_instance_of(Split) then
|
||||
type = "split"
|
||||
end
|
||||
---@diagnostic enable: undefined-field
|
||||
|
||||
if not type then
|
||||
error("unsupported component")
|
||||
end
|
||||
|
||||
return {
|
||||
type = type,
|
||||
component = box,
|
||||
grow = options.grow,
|
||||
size = options.size,
|
||||
}
|
||||
end
|
||||
|
||||
local dir = defaults(options.dir, "row")
|
||||
|
||||
-- normalize children size
|
||||
for _, child in ipairs(box) do
|
||||
if not child.grow and not child.size then
|
||||
error("missing child.size")
|
||||
end
|
||||
|
||||
if dir == "row" then
|
||||
if type(child.size) ~= "table" then
|
||||
---@diagnostic disable-next-line: assign-type-mismatch
|
||||
child.size = { width = child.size }
|
||||
end
|
||||
if not child.size.height then
|
||||
child.size.height = "100%"
|
||||
end
|
||||
elseif dir == "col" then
|
||||
if not is_type("table", child.size) then
|
||||
---@diagnostic disable-next-line: assign-type-mismatch
|
||||
child.size = { height = child.size }
|
||||
end
|
||||
if not child.size.width then
|
||||
child.size.width = "100%"
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
return {
|
||||
box = box,
|
||||
dir = dir,
|
||||
grow = options.grow,
|
||||
size = options.size,
|
||||
}
|
||||
end
|
||||
|
||||
-- luacheck: push no max comment line length
|
||||
|
||||
---@alias NuiLayout.constructor fun(options: nui_layout_options|NuiPopup|NuiSplit, box: NuiLayout.Box|NuiLayout.Box[]): NuiLayout
|
||||
---@type NuiLayout|NuiLayout.constructor
|
||||
local NuiLayout = Layout
|
||||
|
||||
-- luacheck: pop
|
||||
|
||||
return NuiLayout
|
||||
262
.config/nvim/pack/tree/start/nui.nvim/lua/nui/layout/split.lua
Normal file
262
.config/nvim/pack/tree/start/nui.nvim/lua/nui/layout/split.lua
Normal file
|
|
@ -0,0 +1,262 @@
|
|||
local utils = require("nui.utils")
|
||||
local split_utils = require("nui.split.utils")
|
||||
|
||||
local u = {
|
||||
is_type = utils.is_type,
|
||||
split = split_utils,
|
||||
set_win_options = utils._.set_win_options,
|
||||
}
|
||||
|
||||
local mod = {}
|
||||
|
||||
---@param box_dir '"row"'|'"col"'
|
||||
---@return nui_split_internal_position position
|
||||
local function get_child_position(box_dir)
|
||||
if box_dir == "row" then
|
||||
return "right"
|
||||
else
|
||||
return "bottom"
|
||||
end
|
||||
end
|
||||
|
||||
---@param position nui_split_internal_position
|
||||
---@param child { size: nui_layout_option_size, grow?: boolean }
|
||||
---@param container_size { width?: number, height?: number }
|
||||
---@param growable_dimension_per_factor? number
|
||||
local function get_child_size(position, child, container_size, growable_dimension_per_factor)
|
||||
local child_size
|
||||
if position == "left" or position == "right" then
|
||||
child_size = child.size.width
|
||||
else
|
||||
child_size = child.size.height
|
||||
end
|
||||
|
||||
if child.grow and growable_dimension_per_factor then
|
||||
child_size = math.floor(growable_dimension_per_factor * child.grow)
|
||||
end
|
||||
|
||||
return u.split.calculate_window_size(position, child_size, container_size)
|
||||
end
|
||||
|
||||
local function get_container_size(meta)
|
||||
local size = meta.container_size
|
||||
size.width = size.width or meta.container_fallback_size.width
|
||||
size.height = size.height or meta.container_fallback_size.height
|
||||
return size
|
||||
end
|
||||
|
||||
function mod.process(box, meta)
|
||||
-- luacov: disable
|
||||
if box.mount or box.component or not box.box then
|
||||
return error("invalid parameter: box")
|
||||
end
|
||||
-- luacov: enable
|
||||
|
||||
local container_size = get_container_size(meta)
|
||||
|
||||
-- luacov: disable
|
||||
if not u.is_type("number", container_size.width) and not u.is_type("number", container_size.height) then
|
||||
return error("invalid value: box.size")
|
||||
end
|
||||
-- luacov: enable
|
||||
|
||||
local consumed_size = {
|
||||
width = 0,
|
||||
height = 0,
|
||||
}
|
||||
|
||||
local growable_child_factor = 0
|
||||
|
||||
for i, child in ipairs(box.box) do
|
||||
if meta.process_growable_child or not child.grow then
|
||||
local position = get_child_position(box.dir)
|
||||
local relative = { type = "win" }
|
||||
local size = get_child_size(position, child, container_size, meta.growable_dimension_per_factor)
|
||||
|
||||
consumed_size.width = consumed_size.width + (size.width or 0)
|
||||
consumed_size.height = consumed_size.height + (size.height or 0)
|
||||
|
||||
if i == 1 then
|
||||
position = meta.position
|
||||
if meta.relative then
|
||||
relative = meta.relative
|
||||
end
|
||||
if position == "left" or position == "right" then
|
||||
size.width = container_size.width
|
||||
else
|
||||
size.height = container_size.height
|
||||
end
|
||||
end
|
||||
|
||||
if child.component then
|
||||
child.component:update_layout({
|
||||
position = position,
|
||||
relative = relative,
|
||||
size = size,
|
||||
})
|
||||
if i == 1 and child.component.winid then
|
||||
if position == "left" or position == "right" then
|
||||
vim.api.nvim_win_set_height(child.component.winid, size.height)
|
||||
else
|
||||
vim.api.nvim_win_set_width(child.component.winid, size.width)
|
||||
end
|
||||
end
|
||||
else
|
||||
mod.process(child, {
|
||||
container_size = size,
|
||||
container_fallback_size = container_size,
|
||||
position = position,
|
||||
})
|
||||
end
|
||||
end
|
||||
|
||||
if child.grow then
|
||||
growable_child_factor = growable_child_factor + child.grow
|
||||
end
|
||||
end
|
||||
|
||||
if meta.process_growable_child or growable_child_factor == 0 then
|
||||
return
|
||||
end
|
||||
|
||||
local growable_width = container_size.width - consumed_size.width
|
||||
local growable_height = container_size.height - consumed_size.height
|
||||
local growable_dimension = box.dir == "col" and growable_height or growable_width
|
||||
local growable_dimension_per_factor = growable_dimension / growable_child_factor
|
||||
|
||||
mod.process(box, {
|
||||
container_size = meta.container_size,
|
||||
container_fallback_size = meta.container_fallback_size,
|
||||
position = meta.position,
|
||||
process_growable_child = true,
|
||||
growable_dimension_per_factor = growable_dimension_per_factor,
|
||||
})
|
||||
end
|
||||
|
||||
---@param box table Layout.Box
|
||||
local function get_first_component(box)
|
||||
if not box.box[1] then
|
||||
return
|
||||
end
|
||||
|
||||
if box.box[1].component then
|
||||
return box.box[1].component
|
||||
end
|
||||
|
||||
return get_first_component(box.box[1])
|
||||
end
|
||||
|
||||
---@param box table Layout.Box
|
||||
local function unset_win_options_fixsize(box)
|
||||
for _, child in ipairs(box.box) do
|
||||
if child.component then
|
||||
local winfix = child.component._._layout_orig_winfixsize
|
||||
if winfix then
|
||||
child.component._.win_options.winfixwidth = winfix.winfixwidth
|
||||
child.component._.win_options.winfixheight = winfix.winfixheight
|
||||
child.component._._layout_orig_winfixsize = nil
|
||||
end
|
||||
u.set_win_options(child.component.winid, {
|
||||
winfixwidth = child.component._.win_options.winfixwidth,
|
||||
winfixheight = child.component._.win_options.winfixheight,
|
||||
})
|
||||
else
|
||||
unset_win_options_fixsize(child)
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
---@param box table Layout.Box
|
||||
---@param action '"mount"'|'"show"'
|
||||
---@param meta? { initial_pass?: boolean }
|
||||
local function do_action(box, action, meta)
|
||||
meta = meta or { root = true }
|
||||
|
||||
for i, child in ipairs(box.box) do
|
||||
if not meta.initial_pass or i == 1 then
|
||||
if child.component then
|
||||
child.component._._layout_orig_winfixsize = {
|
||||
winfixwidth = child.component._.win_options.winfixwidth,
|
||||
winfixheight = child.component._.win_options.winfixheight,
|
||||
}
|
||||
|
||||
child.component._.win_options.winfixwidth = i ~= 1
|
||||
child.component._.win_options.winfixheight = i == 1
|
||||
if box.dir == "col" then
|
||||
child.component._.win_options.winfixwidth = not child.component._.win_options.winfixwidth
|
||||
child.component._.win_options.winfixheight = not child.component._.win_options.winfixheight
|
||||
end
|
||||
|
||||
if child.component and not child.component.winid then
|
||||
child.component._.relative.win = vim.api.nvim_get_current_win()
|
||||
child.component._.win_config.win = child.component._.relative.win
|
||||
end
|
||||
|
||||
child.component[action](child.component)
|
||||
|
||||
if action == "show" and not child.component._.mounted then
|
||||
child.component:mount()
|
||||
end
|
||||
else
|
||||
do_action(child, action, {
|
||||
initial_pass = true,
|
||||
})
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
if not meta.initial_pass then
|
||||
for _, child in ipairs(box.box) do
|
||||
if child.box then
|
||||
local first_component = get_first_component(child)
|
||||
if first_component and first_component.winid then
|
||||
vim.api.nvim_set_current_win(first_component.winid)
|
||||
end
|
||||
|
||||
do_action(child, action, {
|
||||
initial_pass = false,
|
||||
})
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
if meta.root then
|
||||
unset_win_options_fixsize(box)
|
||||
end
|
||||
end
|
||||
|
||||
---@param box table Layout.Box
|
||||
---@param meta? { initial_pass?: boolean }
|
||||
function mod.mount_box(box, meta)
|
||||
do_action(box, "mount", meta)
|
||||
end
|
||||
|
||||
---@param box table Layout.Box
|
||||
---@param meta? { initial_pass?: boolean }
|
||||
function mod.show_box(box, meta)
|
||||
do_action(box, "show", meta)
|
||||
end
|
||||
|
||||
---@param box table Layout.Box
|
||||
function mod.unmount_box(box)
|
||||
for _, child in ipairs(box.box) do
|
||||
if child.component then
|
||||
child.component:unmount()
|
||||
else
|
||||
mod.unmount_box(child)
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
---@param box table Layout.Box
|
||||
function mod.hide_box(box)
|
||||
for _, child in ipairs(box.box) do
|
||||
if child.component then
|
||||
child.component:hide()
|
||||
else
|
||||
mod.hide_box(child)
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
return mod
|
||||
226
.config/nvim/pack/tree/start/nui.nvim/lua/nui/layout/utils.lua
Normal file
226
.config/nvim/pack/tree/start/nui.nvim/lua/nui/layout/utils.lua
Normal file
|
|
@ -0,0 +1,226 @@
|
|||
local utils = require("nui.utils")
|
||||
|
||||
local _ = utils._
|
||||
local defaults = utils.defaults
|
||||
|
||||
--luacheck: push no max line length
|
||||
|
||||
---@alias nui_layout_option_anchor "NW"|"NE"|"SW"|"SE"
|
||||
---@alias nui_layout_option_relative_type "'cursor'"|"'editor'"|"'win'"|"'buf'"
|
||||
---@alias nui_layout_option_relative { type: nui_layout_option_relative_type, winid?: number, position?: { row: number, col: number } }
|
||||
---@alias nui_layout_option_position { row: number|string, col: number|string }
|
||||
---@alias nui_layout_option_size { width: number|string, height: number|string }
|
||||
---@alias nui_layout_internal_position { relative: "'cursor'"|"'editor'"|"'win'", win: number, bufpos?: number[], row: number, col: number }
|
||||
---@alias nui_layout_container_info { relative: nui_layout_option_relative_type, size: { height: integer, width: integer }, type: "'editor'"|"'window'" }
|
||||
|
||||
--luacheck: pop
|
||||
|
||||
local mod_size = {}
|
||||
local mod_position = {}
|
||||
|
||||
local mod = {
|
||||
size = mod_size,
|
||||
position = mod_position,
|
||||
}
|
||||
|
||||
---@param position nui_layout_option_position
|
||||
---@param size { width: number, height: number }
|
||||
---@param container nui_layout_container_info
|
||||
---@return { row: number, col: number }
|
||||
function mod.calculate_window_position(position, size, container)
|
||||
local row
|
||||
local col
|
||||
|
||||
local is_percentage_allowed = not vim.tbl_contains({ "buf", "cursor" }, container.relative)
|
||||
local percentage_error = string.format("position %% can not be used relative to %s", container.relative)
|
||||
|
||||
local r = utils.parse_number_input(position.row)
|
||||
assert(r.value ~= nil, "invalid position.row")
|
||||
if r.is_percentage then
|
||||
assert(is_percentage_allowed, percentage_error)
|
||||
row = math.floor((container.size.height - size.height) * r.value)
|
||||
else
|
||||
row = r.value
|
||||
end
|
||||
|
||||
local c = utils.parse_number_input(position.col)
|
||||
assert(c.value ~= nil, "invalid position.col")
|
||||
if c.is_percentage then
|
||||
assert(is_percentage_allowed, percentage_error)
|
||||
col = math.floor((container.size.width - size.width) * c.value)
|
||||
else
|
||||
col = c.value
|
||||
end
|
||||
|
||||
return {
|
||||
row = row,
|
||||
col = col,
|
||||
}
|
||||
end
|
||||
|
||||
---@param size { width: number|string, height: number|string }
|
||||
---@param container_size { width: number, height: number }
|
||||
---@return { width: number, height: number }
|
||||
function mod.calculate_window_size(size, container_size)
|
||||
local width = _.normalize_dimension(size.width, container_size.width)
|
||||
assert(width, "invalid size.width")
|
||||
|
||||
local height = _.normalize_dimension(size.height, container_size.height)
|
||||
assert(height, "invalid size.height")
|
||||
|
||||
return {
|
||||
width = width,
|
||||
height = height,
|
||||
}
|
||||
end
|
||||
|
||||
---@param position nui_layout_internal_position
|
||||
---@return nui_layout_container_info
|
||||
function mod.get_container_info(position)
|
||||
local relative = position.relative
|
||||
local winid = position.win == 0 and vim.api.nvim_get_current_win() or position.win
|
||||
|
||||
if relative == "editor" then
|
||||
return {
|
||||
relative = relative,
|
||||
size = utils.get_editor_size(),
|
||||
type = "editor",
|
||||
winid = winid,
|
||||
}
|
||||
end
|
||||
|
||||
return {
|
||||
relative = position.bufpos and "buf" or relative,
|
||||
size = utils.get_window_size(position.win),
|
||||
type = "window",
|
||||
winid = winid,
|
||||
}
|
||||
end
|
||||
|
||||
---@param relative nui_layout_option_relative
|
||||
---@param fallback_winid number
|
||||
---@return nui_layout_internal_position
|
||||
function mod.parse_relative(relative, fallback_winid)
|
||||
local winid = defaults(relative.winid, fallback_winid)
|
||||
|
||||
if relative.type == "buf" then
|
||||
return {
|
||||
relative = "win",
|
||||
win = winid,
|
||||
bufpos = {
|
||||
relative.position.row,
|
||||
relative.position.col,
|
||||
},
|
||||
}
|
||||
end
|
||||
|
||||
return {
|
||||
relative = relative.type,
|
||||
win = winid,
|
||||
}
|
||||
end
|
||||
|
||||
---@param component_internal table
|
||||
---@param config nui_layout_options
|
||||
function mod.update_layout_config(component_internal, config)
|
||||
local internal = component_internal
|
||||
|
||||
local options = _.normalize_layout_options({
|
||||
relative = config.relative,
|
||||
size = config.size,
|
||||
position = config.position,
|
||||
})
|
||||
|
||||
local win_config = internal.win_config
|
||||
|
||||
if config.anchor then
|
||||
win_config.anchor = config.anchor
|
||||
end
|
||||
|
||||
if options.relative then
|
||||
internal.layout.relative = options.relative
|
||||
|
||||
local fallback_winid = internal.position and internal.position.win
|
||||
or internal.layout.relative.type == "cursor" and 0
|
||||
or vim.api.nvim_get_current_win()
|
||||
internal.position =
|
||||
vim.tbl_extend("force", internal.position or {}, mod.parse_relative(internal.layout.relative, fallback_winid))
|
||||
|
||||
win_config.relative = internal.position.relative
|
||||
win_config.win = internal.position.relative == "win" and internal.position.win or nil
|
||||
win_config.bufpos = internal.position.bufpos
|
||||
end
|
||||
|
||||
-- luacov: disable
|
||||
if not win_config.relative then
|
||||
return error("missing layout config: relative")
|
||||
end
|
||||
-- luacov: enable
|
||||
|
||||
local prev_container_size = internal.container_info and internal.container_info.size
|
||||
internal.container_info = mod.get_container_info(internal.position)
|
||||
local container_size_changed = not mod.size.are_same(internal.container_info.size, prev_container_size)
|
||||
|
||||
if
|
||||
options.size
|
||||
-- need_size_refresh
|
||||
or (container_size_changed and internal.layout.size and mod.size.contains_percentage_string(internal.layout.size))
|
||||
then
|
||||
internal.layout.size = options.size or internal.layout.size
|
||||
|
||||
internal.size = mod.calculate_window_size(internal.layout.size, internal.container_info.size)
|
||||
|
||||
win_config.width = internal.size.width
|
||||
win_config.height = internal.size.height
|
||||
end
|
||||
|
||||
if not win_config.width or not win_config.height then
|
||||
return error("missing layout config: size")
|
||||
end
|
||||
|
||||
if
|
||||
options.position
|
||||
-- need_position_refresh
|
||||
or (
|
||||
container_size_changed
|
||||
and internal.layout.position
|
||||
and mod.position.contains_percentage_string(internal.layout.position)
|
||||
)
|
||||
then
|
||||
internal.layout.position = options.position or internal.layout.position
|
||||
|
||||
internal.position = vim.tbl_extend(
|
||||
"force",
|
||||
internal.position,
|
||||
mod.calculate_window_position(internal.layout.position, internal.size, internal.container_info)
|
||||
)
|
||||
|
||||
win_config.row = internal.position.row
|
||||
win_config.col = internal.position.col
|
||||
end
|
||||
|
||||
if not win_config.row or not win_config.col then
|
||||
return error("missing layout config: position")
|
||||
end
|
||||
end
|
||||
|
||||
---@param size_a nui_layout_option_size
|
||||
---@param size_b? nui_layout_option_size
|
||||
---@return boolean
|
||||
function mod_size.are_same(size_a, size_b)
|
||||
return size_b and size_a.width == size_b.width and size_a.height == size_b.height or false
|
||||
end
|
||||
|
||||
---@param size nui_layout_option_size
|
||||
---@return boolean
|
||||
function mod_size.contains_percentage_string(size)
|
||||
return type(size.width) == "string" or type(size.height) == "string"
|
||||
end
|
||||
|
||||
---@param position nui_layout_option_position
|
||||
---@return boolean
|
||||
function mod_position.contains_percentage_string(position)
|
||||
return type(position.row) == "string" or type(position.col) == "string"
|
||||
end
|
||||
|
||||
return mod
|
||||
100
.config/nvim/pack/tree/start/nui.nvim/lua/nui/line/README.md
Normal file
100
.config/nvim/pack/tree/start/nui.nvim/lua/nui/line/README.md
Normal file
|
|
@ -0,0 +1,100 @@
|
|||
# NuiLine
|
||||
|
||||
NuiLine is an abstraction layer on top of the following native functions:
|
||||
|
||||
- `vim.api.nvim_buf_set_lines` (check `:h nvim_buf_set_lines()`)
|
||||
- `vim.api.nvim_buf_set_text` (check `:h nvim_buf_set_text()`)
|
||||
- `vim.api.nvim_buf_add_highlight` (check `:h nvim_buf_add_highlight()`)
|
||||
|
||||
It helps you create line on the buffer containing multiple [`NuiText`](../text)s.
|
||||
|
||||
_Signature:_ `NuiLine(texts?)`
|
||||
|
||||
**Example**
|
||||
|
||||
```lua
|
||||
local NuiLine = require("nui.line")
|
||||
|
||||
local line = NuiLine()
|
||||
|
||||
line:append("Something Went Wrong!", "Error")
|
||||
|
||||
local bufnr, ns_id, linenr_start = 0, -1, 1
|
||||
|
||||
line:render(bufnr, ns_id, linenr_start)
|
||||
```
|
||||
|
||||
## Parameters
|
||||
|
||||
### `texts`
|
||||
|
||||
**Type:** `table[]`
|
||||
|
||||
List of `NuiText` objects to set as initial texts.
|
||||
|
||||
**Example**
|
||||
|
||||
```lua
|
||||
local text_one = NuiText("One")
|
||||
local text_two = NuiText("Two")
|
||||
local line = NuiLine({ text_one, text_two })
|
||||
```
|
||||
|
||||
## Methods
|
||||
|
||||
### `line:append`
|
||||
|
||||
_Signature:_ `line:append(content, highlight?)`
|
||||
|
||||
Adds a chunk of content to the line.
|
||||
|
||||
**Parameters**
|
||||
|
||||
| Name | Type | Description |
|
||||
| ----------- | -------------------------------- | --------------------- |
|
||||
| `content` | `string` / `NuiText` / `NuiLine` | content |
|
||||
| `highlight` | `string` or `table` | highlight information |
|
||||
|
||||
If `text` is `string`, these parameters are passed to `NuiText`
|
||||
and a `NuiText` object is returned.
|
||||
|
||||
It `content` is a `NuiText`/`NuiLine` object, it is returned unchanged.
|
||||
|
||||
### `line:content`
|
||||
|
||||
_Signature:_ `line:content()`
|
||||
|
||||
Returns the line content.
|
||||
|
||||
### `line:highlight`
|
||||
|
||||
_Signature:_ `line:highlight(bufnr, ns_id, linenr)`
|
||||
|
||||
Applies highlight for the line.
|
||||
|
||||
**Parameters**
|
||||
|
||||
| Name | Type | Description |
|
||||
| -------- | -------- | ---------------------------------------------- |
|
||||
| `bufnr` | `number` | buffer number |
|
||||
| `ns_id` | `number` | namespace id (use `-1` for fallback namespace) |
|
||||
| `linenr` | `number` | line number (1-indexed) |
|
||||
|
||||
### `line:render`
|
||||
|
||||
_Signature:_ `line:render(bufnr, ns_id, linenr_start, linenr_end?)`
|
||||
|
||||
Sets the line on buffer and applies highlight.
|
||||
|
||||
**Parameters**
|
||||
|
||||
| Name | Type | Description |
|
||||
| -------------- | -------- | ---------------------------------------------- |
|
||||
| `bufnr` | `number` | buffer number |
|
||||
| `ns_id` | `number` | namespace id (use `-1` for fallback namespace) |
|
||||
| `linenr_start` | `number` | start line number (1-indexed) |
|
||||
| `linenr_end` | `number` | end line number (1-indexed) |
|
||||
|
||||
## Wiki Page
|
||||
|
||||
You can find additional documentation/examples/guides/tips-n-tricks in [nui.line wiki page](https://github.com/MunifTanjim/nui.nvim/wiki/nui.line).
|
||||
80
.config/nvim/pack/tree/start/nui.nvim/lua/nui/line/init.lua
Normal file
80
.config/nvim/pack/tree/start/nui.nvim/lua/nui/line/init.lua
Normal file
|
|
@ -0,0 +1,80 @@
|
|||
local Object = require("nui.object")
|
||||
local NuiText = require("nui.text")
|
||||
local defaults = require("nui.utils").defaults
|
||||
|
||||
---@class NuiLine
|
||||
---@field _texts NuiText[]
|
||||
local Line = Object("NuiLine")
|
||||
|
||||
---@param texts? NuiText[]
|
||||
function Line:init(texts)
|
||||
self._texts = defaults(texts, {})
|
||||
end
|
||||
|
||||
---@param content string|NuiText|NuiLine
|
||||
---@param highlight? string|nui_text_extmark data for highlight
|
||||
---@return NuiText|NuiLine
|
||||
function Line:append(content, highlight)
|
||||
local block = content
|
||||
if type(block) == "string" then
|
||||
block = NuiText(block, highlight)
|
||||
end
|
||||
if block._texts then
|
||||
---@cast block NuiLine
|
||||
for _, text in ipairs(block._texts) do
|
||||
table.insert(self._texts, text)
|
||||
end
|
||||
else
|
||||
---@cast block NuiText
|
||||
table.insert(self._texts, block)
|
||||
end
|
||||
return block
|
||||
end
|
||||
|
||||
---@return string
|
||||
function Line:content()
|
||||
return table.concat(vim.tbl_map(function(text)
|
||||
return text:content()
|
||||
end, self._texts))
|
||||
end
|
||||
|
||||
---@return number
|
||||
function Line:width()
|
||||
local width = 0
|
||||
for _, text in ipairs(self._texts) do
|
||||
width = width + text:width()
|
||||
end
|
||||
return width
|
||||
end
|
||||
|
||||
---@param bufnr number buffer number
|
||||
---@param ns_id number namespace id
|
||||
---@param linenr number line number (1-indexed)
|
||||
---@param ___byte_start___? integer start byte position (0-indexed)
|
||||
---@return nil
|
||||
function Line:highlight(bufnr, ns_id, linenr, ___byte_start___)
|
||||
local current_byte_start = ___byte_start___ or 0
|
||||
for _, text in ipairs(self._texts) do
|
||||
text:highlight(bufnr, ns_id, linenr, current_byte_start)
|
||||
current_byte_start = current_byte_start + text:length()
|
||||
end
|
||||
end
|
||||
|
||||
---@param bufnr number buffer number
|
||||
---@param ns_id number namespace id
|
||||
---@param linenr_start number start line number (1-indexed)
|
||||
---@param linenr_end? number end line number (1-indexed)
|
||||
---@return nil
|
||||
function Line:render(bufnr, ns_id, linenr_start, linenr_end)
|
||||
local row_start = linenr_start - 1
|
||||
local row_end = linenr_end and linenr_end - 1 or row_start + 1
|
||||
local content = self:content()
|
||||
vim.api.nvim_buf_set_lines(bufnr, row_start, row_end, false, { content })
|
||||
self:highlight(bufnr, ns_id, linenr_start)
|
||||
end
|
||||
|
||||
---@alias NuiLine.constructor fun(texts?: NuiText[]): NuiLine
|
||||
---@type NuiLine|NuiLine.constructor
|
||||
local NuiLine = Line
|
||||
|
||||
return NuiLine
|
||||
207
.config/nvim/pack/tree/start/nui.nvim/lua/nui/menu/README.md
Normal file
207
.config/nvim/pack/tree/start/nui.nvim/lua/nui/menu/README.md
Normal file
|
|
@ -0,0 +1,207 @@
|
|||
# Menu
|
||||
|
||||
`Menu` is abstraction layer on top of `Popup`.
|
||||
|
||||
```lua
|
||||
local Menu = require("nui.menu")
|
||||
local event = require("nui.utils.autocmd").event
|
||||
|
||||
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,
|
||||
})
|
||||
```
|
||||
|
||||
You can manipulate the associated buffer and window using the
|
||||
`split.bufnr` and `split.winid` properties.
|
||||
|
||||
**NOTE**: the first argument accepts options for `nui.popup` component.
|
||||
|
||||
## Options
|
||||
|
||||
### `lines`
|
||||
|
||||
**Type:** `table`
|
||||
|
||||
List of menu items.
|
||||
|
||||
**`Menu.item(content, data?)`**
|
||||
|
||||
`Menu.item` is used to create an item object for the `Menu`.
|
||||
|
||||
**Parameters**
|
||||
|
||||
| Name | Type |
|
||||
| --------- | -------------------------------- |
|
||||
| `content` | `string` / `NuiText` / `NuiLine` |
|
||||
| `data` | `table` / `nil` |
|
||||
|
||||
**Example**
|
||||
|
||||
```lua
|
||||
Menu.item("One") --> { text = "One" }
|
||||
|
||||
Menu.item("Two", { id = 2 }) --> { id = 2, text = "Two" }
|
||||
```
|
||||
|
||||
This is what you get as the argument of `on_submit` callback function.
|
||||
You can include whatever you want in the item object.
|
||||
|
||||
**`Menu.separator(content?, options?)`**
|
||||
|
||||
`Menu.separator` is used to create a menu item that can't be focused.
|
||||
|
||||
**Parameters**
|
||||
|
||||
| Name | Type |
|
||||
| --------- | ---------------------------------------------------------------------------------- |
|
||||
| `content` | `string` / `NuiText` / `NuiLine` / `nil` |
|
||||
| `options` | `{ char?: string\|NuiText, text_align?: "'left'"\|"'center'"\|"'right'" }` / `nil` |
|
||||
|
||||
You can just use `Menu.item` only and implement `Menu.separator`'s behavior
|
||||
by providing a custom `should_skip_item` function.
|
||||
|
||||
### `prepare_item`
|
||||
|
||||
**Type:** `function`
|
||||
|
||||
_Signature:_ `prepare_item(item)`
|
||||
|
||||
If provided, this function is used for preparing each menu item.
|
||||
|
||||
The return value should be a `NuiLine` object or `string` or a list containing either of them.
|
||||
|
||||
If return value is `nil`, that node will not be rendered.
|
||||
|
||||
### `should_skip_item`
|
||||
|
||||
**Type:** `function`
|
||||
|
||||
_Signature:_ `should_skip_item(item)`
|
||||
|
||||
If provided, this function is used to determine if an item should be
|
||||
skipped when focusing previous/next item.
|
||||
|
||||
The return value should be `boolean`.
|
||||
|
||||
By default, items created by `Menu.separator` are skipped.
|
||||
|
||||
### `max_height`
|
||||
|
||||
**Type:** `number`
|
||||
|
||||
Maximum height of the menu.
|
||||
|
||||
### `min_height`
|
||||
|
||||
**Type:** `number`
|
||||
|
||||
Minimum height of the menu.
|
||||
|
||||
### `max_width`
|
||||
|
||||
**Type:** `number`
|
||||
|
||||
Maximum width of the menu.
|
||||
|
||||
### `min_width`
|
||||
|
||||
**Type:** `number`
|
||||
|
||||
Minimum width of the menu.
|
||||
|
||||
### `keymap`
|
||||
|
||||
**Type:** `table`
|
||||
|
||||
Key mappings for the menu.
|
||||
|
||||
**Example**
|
||||
|
||||
```lua
|
||||
keymap = {
|
||||
close = { "<Esc>", "<C-c>" },
|
||||
focus_next = { "j", "<Down>", "<Tab>" },
|
||||
focus_prev = { "k", "<Up>", "<S-Tab>" },
|
||||
submit = { "<CR>" },
|
||||
},
|
||||
```
|
||||
|
||||
### `on_change`
|
||||
|
||||
**Type:** `function`
|
||||
|
||||
_Signature:_ `on_change(item, menu) -> nil`
|
||||
|
||||
Callback function, called when menu item is focused.
|
||||
|
||||
### `on_close`
|
||||
|
||||
**Type:** `function`
|
||||
|
||||
_Signature:_ `on_close() -> nil`
|
||||
|
||||
Callback function, called when menu is closed.
|
||||
|
||||
### `on_submit`
|
||||
|
||||
**Type:** `function`
|
||||
|
||||
_Signature:_ `on_submit(item) -> nil`
|
||||
|
||||
Callback function, called when menu is submitted.
|
||||
|
||||
## Methods
|
||||
|
||||
Methods from `nui.popup` are also available for `nui.menu`.
|
||||
|
||||
## Properties
|
||||
|
||||
### `menu.tree`
|
||||
|
||||
The underlying `NuiTree` object used for rendering the menu. You can use it to
|
||||
manipulate the menu items on-the-fly and access all the `NuiTree` methods.
|
||||
|
||||
## Wiki Page
|
||||
|
||||
You can find additional documentation/examples/guides/tips-n-tricks in [nui.menu wiki page](https://github.com/MunifTanjim/nui.nvim/wiki/nui.menu).
|
||||
377
.config/nvim/pack/tree/start/nui.nvim/lua/nui/menu/init.lua
Normal file
377
.config/nvim/pack/tree/start/nui.nvim/lua/nui/menu/init.lua
Normal file
|
|
@ -0,0 +1,377 @@
|
|||
local Line = require("nui.line")
|
||||
local Popup = require("nui.popup")
|
||||
local Text = require("nui.text")
|
||||
local Tree = require("nui.tree")
|
||||
local _ = require("nui.utils")._
|
||||
local defaults = require("nui.utils").defaults
|
||||
local is_type = require("nui.utils").is_type
|
||||
|
||||
local function calculate_initial_max_width(items)
|
||||
local max_width = 0
|
||||
|
||||
for _, item in ipairs(items) do
|
||||
local width = 0
|
||||
if is_type("string", item.text) then
|
||||
width = vim.api.nvim_strwidth(item.text)
|
||||
elseif is_type("table", item.text) and item.text.width then
|
||||
width = item.text:width()
|
||||
end
|
||||
|
||||
if max_width < width then
|
||||
max_width = width
|
||||
end
|
||||
end
|
||||
|
||||
return max_width
|
||||
end
|
||||
|
||||
local default_keymap = {
|
||||
close = { "<Esc>", "<C-c>" },
|
||||
focus_next = { "j", "<Down>", "<Tab>" },
|
||||
focus_prev = { "k", "<Up>", "<S-Tab>" },
|
||||
submit = { "<CR>" },
|
||||
}
|
||||
|
||||
---@param keymap table<_nui_menu_keymap_action, string|string[]>
|
||||
---@return table<_nui_menu_keymap_action, string[]>
|
||||
local function parse_keymap(keymap)
|
||||
local result = defaults(keymap, {})
|
||||
|
||||
for name, default_keys in pairs(default_keymap) do
|
||||
if is_type("nil", result[name]) then
|
||||
result[name] = default_keys
|
||||
elseif is_type("string", result[name]) then
|
||||
result[name] = { result[name] }
|
||||
end
|
||||
end
|
||||
|
||||
return result
|
||||
end
|
||||
|
||||
---@type nui_menu_should_skip_item
|
||||
local function default_should_skip_item(node)
|
||||
return node._type == "separator"
|
||||
end
|
||||
|
||||
---@param menu NuiMenu
|
||||
---@return nui_menu_prepare_item
|
||||
local function make_default_prepare_node(menu)
|
||||
local border = menu.border
|
||||
|
||||
local fallback_sep = {
|
||||
char = Text(is_type("table", border._.char) and border._.char.top or " "),
|
||||
text_align = is_type("table", border._.text) and border._.text.top_align or "left",
|
||||
}
|
||||
|
||||
-- luacov: disable
|
||||
if menu._.sep then
|
||||
-- @deprecated
|
||||
|
||||
if menu._.sep.char then
|
||||
fallback_sep.char = Text(menu._.sep.char)
|
||||
end
|
||||
|
||||
if menu._.sep.text_align then
|
||||
fallback_sep.text_align = menu._.sep.text_align
|
||||
end
|
||||
end
|
||||
-- luacov: enable
|
||||
|
||||
local max_width = menu._.size.width
|
||||
|
||||
---@type nui_menu_prepare_item
|
||||
local function default_prepare_node(node)
|
||||
---@type NuiText|NuiLine
|
||||
local content = is_type("string", node.text) and Text(node.text) or node.text
|
||||
|
||||
if node._type == "item" then
|
||||
if content:width() > max_width then
|
||||
if is_type("function", content.set) then
|
||||
---@cast content NuiText
|
||||
_.truncate_nui_text(content, max_width)
|
||||
else
|
||||
---@cast content NuiLine
|
||||
_.truncate_nui_line(content, max_width)
|
||||
end
|
||||
end
|
||||
|
||||
local line = Line()
|
||||
|
||||
line:append(content)
|
||||
|
||||
return line
|
||||
end
|
||||
|
||||
if node._type == "separator" then
|
||||
local sep_char = Text(defaults(node._char, fallback_sep.char))
|
||||
local sep_text_align = defaults(node._text_align, fallback_sep.text_align)
|
||||
|
||||
local sep_max_width = max_width - sep_char:width() * 2
|
||||
|
||||
if content:width() > sep_max_width then
|
||||
if content._texts then
|
||||
---@cast content NuiLine
|
||||
_.truncate_nui_line(content, sep_max_width)
|
||||
else
|
||||
---@cast content NuiText
|
||||
_.truncate_nui_text(content, sep_max_width)
|
||||
end
|
||||
end
|
||||
|
||||
local left_gap_width, right_gap_width =
|
||||
_.calculate_gap_width(defaults(sep_text_align, "center"), sep_max_width, content:width())
|
||||
|
||||
local line = Line()
|
||||
|
||||
line:append(Text(sep_char))
|
||||
|
||||
if left_gap_width > 0 then
|
||||
line:append(Text(sep_char):set(string.rep(sep_char:content(), left_gap_width)))
|
||||
end
|
||||
|
||||
line:append(content)
|
||||
|
||||
if right_gap_width > 0 then
|
||||
line:append(Text(sep_char):set(string.rep(sep_char:content(), right_gap_width)))
|
||||
end
|
||||
|
||||
line:append(Text(sep_char))
|
||||
|
||||
return line
|
||||
end
|
||||
end
|
||||
|
||||
return default_prepare_node
|
||||
end
|
||||
|
||||
---@param menu NuiMenu
|
||||
---@param direction "'next'" | "'prev'"
|
||||
---@param current_linenr nil | number
|
||||
local function focus_item(menu, direction, current_linenr)
|
||||
local curr_linenr = current_linenr or vim.api.nvim_win_get_cursor(menu.winid)[1]
|
||||
|
||||
local next_linenr = nil
|
||||
|
||||
if direction == "next" then
|
||||
if curr_linenr == #menu.tree:get_nodes() then
|
||||
next_linenr = 1
|
||||
else
|
||||
next_linenr = curr_linenr + 1
|
||||
end
|
||||
elseif direction == "prev" then
|
||||
if curr_linenr == 1 then
|
||||
next_linenr = #menu.tree:get_nodes()
|
||||
else
|
||||
next_linenr = curr_linenr - 1
|
||||
end
|
||||
end
|
||||
|
||||
local next_node = menu.tree:get_node(next_linenr)
|
||||
|
||||
if menu._.should_skip_item(next_node) then
|
||||
return focus_item(menu, direction, next_linenr)
|
||||
end
|
||||
|
||||
if next_linenr then
|
||||
vim.api.nvim_win_set_cursor(menu.winid, { next_linenr, 0 })
|
||||
menu._.on_change(next_node)
|
||||
end
|
||||
end
|
||||
|
||||
---@alias nui_menu_prepare_item nui_tree_prepare_node
|
||||
---@alias nui_menu_should_skip_item fun(node: NuiTree.Node): boolean
|
||||
|
||||
---@alias _nui_menu_keymap_action 'close'|'focus_next'|'focus_prev'|'submit'
|
||||
|
||||
---@class nui_menu_internal: nui_popup_internal
|
||||
---@field items NuiTree.Node[]
|
||||
---@field keymap table<_nui_menu_keymap_action, string[]>
|
||||
---@field sep { char?: string|NuiText, text_align?: nui_t_text_align } # deprecated
|
||||
---@field prepare_item nui_menu_prepare_item
|
||||
---@field should_skip_item nui_menu_should_skip_item
|
||||
---@field on_change fun(item: NuiTree.Node): nil
|
||||
|
||||
---@class nui_menu_options
|
||||
---@field lines NuiTree.Node[]
|
||||
---@field prepare_item? nui_tree_prepare_node
|
||||
---@field should_skip_item? nui_menu_should_skip_item
|
||||
---@field max_height? integer
|
||||
---@field min_height? integer
|
||||
---@field max_width? integer
|
||||
---@field min_width? integer
|
||||
---@field keymap? table<_nui_menu_keymap_action, string|string[]>
|
||||
---@field on_change? fun(item: NuiTree.Node, menu: NuiMenu): nil
|
||||
---@field on_close? fun(): nil
|
||||
---@field on_submit? fun(item: NuiTree.Node): nil
|
||||
|
||||
---@class NuiMenu: NuiPopup
|
||||
---@field private _ nui_menu_internal
|
||||
local Menu = Popup:extend("NuiMenu")
|
||||
|
||||
---@param content? string|NuiText|NuiLine
|
||||
---@param options? { char?: string|NuiText, text_align?: nui_t_text_align }
|
||||
---@return NuiTree.Node
|
||||
function Menu.separator(content, options)
|
||||
options = options or {}
|
||||
return Tree.Node({
|
||||
_id = tostring(math.random()),
|
||||
_type = "separator",
|
||||
_char = options.char,
|
||||
_text_align = options.text_align,
|
||||
text = defaults(content, ""),
|
||||
})
|
||||
end
|
||||
|
||||
---@param content string|NuiText|NuiLine
|
||||
---@param data? table
|
||||
---@return NuiTree.Node
|
||||
function Menu.item(content, data)
|
||||
if not data then
|
||||
---@diagnostic disable-next-line: undefined-field
|
||||
if is_type("table", content) and content.text then
|
||||
---@cast content table
|
||||
data = content
|
||||
else
|
||||
data = { text = content }
|
||||
end
|
||||
else
|
||||
data.text = content
|
||||
end
|
||||
|
||||
data._type = "item"
|
||||
data._id = data.id or tostring(math.random())
|
||||
|
||||
return Tree.Node(data)
|
||||
end
|
||||
|
||||
---@param popup_options nui_popup_options
|
||||
---@param options nui_menu_options
|
||||
function Menu:init(popup_options, options)
|
||||
local max_width = calculate_initial_max_width(options.lines)
|
||||
|
||||
local width = math.max(math.min(max_width, defaults(options.max_width, 256)), defaults(options.min_width, 4))
|
||||
local height = math.max(math.min(#options.lines, defaults(options.max_height, 256)), defaults(options.min_height, 1))
|
||||
|
||||
---@type nui_popup_options
|
||||
popup_options = vim.tbl_deep_extend("force", {
|
||||
enter = true,
|
||||
size = {
|
||||
width = width,
|
||||
height = height,
|
||||
},
|
||||
win_options = {
|
||||
cursorline = true,
|
||||
scrolloff = 1,
|
||||
sidescrolloff = 0,
|
||||
},
|
||||
zindex = 60,
|
||||
}, popup_options)
|
||||
|
||||
Menu.super.init(self, popup_options)
|
||||
|
||||
self._.items = options.lines
|
||||
self._.keymap = parse_keymap(options.keymap)
|
||||
|
||||
---@param node NuiTree.Node
|
||||
self._.on_change = function(node)
|
||||
if options.on_change then
|
||||
options.on_change(node, self)
|
||||
end
|
||||
end
|
||||
|
||||
---@deprecated
|
||||
self._.sep = options.separator
|
||||
|
||||
self._.should_skip_item = defaults(options.should_skip_item, default_should_skip_item)
|
||||
self._.prepare_item = defaults(options.prepare_item, self._.prepare_item)
|
||||
|
||||
self.menu_props = {}
|
||||
|
||||
local props = self.menu_props
|
||||
|
||||
props.on_submit = function()
|
||||
local item = self.tree:get_node()
|
||||
|
||||
self:unmount()
|
||||
|
||||
if options.on_submit then
|
||||
options.on_submit(item)
|
||||
end
|
||||
end
|
||||
|
||||
props.on_close = function()
|
||||
self:unmount()
|
||||
|
||||
if options.on_close then
|
||||
options.on_close()
|
||||
end
|
||||
end
|
||||
|
||||
props.on_focus_next = function()
|
||||
focus_item(self, "next")
|
||||
end
|
||||
|
||||
props.on_focus_prev = function()
|
||||
focus_item(self, "prev")
|
||||
end
|
||||
end
|
||||
|
||||
---@param config? nui_layout_options
|
||||
function Menu:update_layout(config)
|
||||
Menu.super.update_layout(self, config)
|
||||
|
||||
self._.prepare_item = defaults(self._.prepare_item, make_default_prepare_node(self))
|
||||
end
|
||||
|
||||
function Menu:mount()
|
||||
Menu.super.mount(self)
|
||||
|
||||
local props = self.menu_props
|
||||
|
||||
for _, key in pairs(self._.keymap.focus_next) do
|
||||
self:map("n", key, props.on_focus_next, { noremap = true, nowait = true })
|
||||
end
|
||||
|
||||
for _, key in pairs(self._.keymap.focus_prev) do
|
||||
self:map("n", key, props.on_focus_prev, { noremap = true, nowait = true })
|
||||
end
|
||||
|
||||
for _, key in pairs(self._.keymap.close) do
|
||||
self:map("n", key, props.on_close, { noremap = true, nowait = true })
|
||||
end
|
||||
|
||||
for _, key in pairs(self._.keymap.submit) do
|
||||
self:map("n", key, props.on_submit, { noremap = true, nowait = true })
|
||||
end
|
||||
|
||||
self.tree = Tree({
|
||||
winid = self.winid,
|
||||
ns_id = self.ns_id,
|
||||
nodes = self._.items,
|
||||
get_node_id = function(node)
|
||||
return node._id
|
||||
end,
|
||||
prepare_node = self._.prepare_item,
|
||||
})
|
||||
|
||||
---@deprecated
|
||||
self._tree = self.tree
|
||||
|
||||
self.tree:render()
|
||||
|
||||
-- focus first item
|
||||
for linenr = 1, #self.tree:get_nodes() do
|
||||
local node, target_linenr = self.tree:get_node(linenr)
|
||||
if not self._.should_skip_item(node) then
|
||||
vim.api.nvim_win_set_cursor(self.winid, { target_linenr, 0 })
|
||||
self._.on_change(node)
|
||||
break
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
---@alias NuiMenu.constructor fun(popup_options: nui_popup_options, options: nui_menu_options): NuiMenu
|
||||
---@type NuiMenu|NuiMenu.constructor
|
||||
local NuiMenu = Menu
|
||||
|
||||
return NuiMenu
|
||||
170
.config/nvim/pack/tree/start/nui.nvim/lua/nui/object/init.lua
Normal file
170
.config/nvim/pack/tree/start/nui.nvim/lua/nui/object/init.lua
Normal file
|
|
@ -0,0 +1,170 @@
|
|||
-- source: https://github.com/kikito/middleclass
|
||||
|
||||
local idx = {
|
||||
subclasses = { "<nui.utils.object:subclasses>" },
|
||||
}
|
||||
|
||||
local function __tostring(self)
|
||||
return "class " .. self.name
|
||||
end
|
||||
|
||||
local function __call(self, ...)
|
||||
return self:new(...)
|
||||
end
|
||||
|
||||
local function create_index_wrapper(class, index)
|
||||
if type(index) == "table" then
|
||||
return function(self, key)
|
||||
local value = self.class.__meta[key]
|
||||
if value == nil then
|
||||
return index[key]
|
||||
end
|
||||
return value
|
||||
end
|
||||
elseif type(index) == "function" then
|
||||
return function(self, key)
|
||||
local value = self.class.__meta[key]
|
||||
if value == nil then
|
||||
return index(self, key)
|
||||
end
|
||||
return value
|
||||
end
|
||||
else
|
||||
return class.__meta
|
||||
end
|
||||
end
|
||||
|
||||
local function propagate_instance_property(class, key, value)
|
||||
value = key == "__index" and create_index_wrapper(class, value) or value
|
||||
|
||||
class.__meta[key] = value
|
||||
|
||||
for subclass in pairs(class[idx.subclasses]) do
|
||||
if subclass.__properties[key] == nil then
|
||||
propagate_instance_property(subclass, key, value)
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
local function declare_instance_property(class, key, value)
|
||||
class.__properties[key] = value
|
||||
|
||||
if value == nil and class.super then
|
||||
value = class.super.__meta[key]
|
||||
end
|
||||
|
||||
propagate_instance_property(class, key, value)
|
||||
end
|
||||
|
||||
local function is_subclass(subclass, class)
|
||||
if not subclass.super then
|
||||
return false
|
||||
end
|
||||
if subclass.super == class then
|
||||
return true
|
||||
end
|
||||
return is_subclass(subclass.super, class)
|
||||
end
|
||||
|
||||
local function is_instance(instance, class)
|
||||
if instance.class == class then
|
||||
return true
|
||||
end
|
||||
return is_subclass(instance.class, class)
|
||||
end
|
||||
|
||||
local function create_class(name, super)
|
||||
assert(name, "missing name")
|
||||
|
||||
local meta = {
|
||||
is_instance_of = is_instance,
|
||||
}
|
||||
meta.__index = meta
|
||||
|
||||
local class = {
|
||||
super = super,
|
||||
name = name,
|
||||
static = {
|
||||
is_subclass_of = is_subclass,
|
||||
},
|
||||
|
||||
[idx.subclasses] = setmetatable({}, { __mode = "k" }),
|
||||
|
||||
__meta = meta,
|
||||
__properties = {},
|
||||
}
|
||||
|
||||
setmetatable(class.static, {
|
||||
__index = function(_, key)
|
||||
local value = rawget(class.__meta, key)
|
||||
if value == nil and super then
|
||||
return super.static[key]
|
||||
end
|
||||
return value
|
||||
end,
|
||||
})
|
||||
|
||||
setmetatable(class, {
|
||||
__call = __call,
|
||||
__index = class.static,
|
||||
__name = class.name,
|
||||
__newindex = declare_instance_property,
|
||||
__tostring = __tostring,
|
||||
})
|
||||
|
||||
return class
|
||||
end
|
||||
|
||||
---@param name string
|
||||
local function create_object(_, name)
|
||||
local Class = create_class(name)
|
||||
|
||||
---@return string
|
||||
function Class:__tostring()
|
||||
return "instance of " .. tostring(self.class)
|
||||
end
|
||||
|
||||
---@return nil
|
||||
function Class:init() end -- luacheck: no unused args
|
||||
|
||||
function Class.static:new(...)
|
||||
local instance = setmetatable({ class = self }, self.__meta)
|
||||
instance:init(...)
|
||||
return instance
|
||||
end
|
||||
|
||||
---@param name string
|
||||
function Class.static:extend(name) -- luacheck: no redefined
|
||||
local subclass = create_class(name, self)
|
||||
|
||||
for key, value in pairs(self.__meta) do
|
||||
if not (key == "__index" and type(value) == "table") then
|
||||
propagate_instance_property(subclass, key, value)
|
||||
end
|
||||
end
|
||||
|
||||
function subclass.init(instance, ...)
|
||||
self.init(instance, ...)
|
||||
end
|
||||
|
||||
self[idx.subclasses][subclass] = true
|
||||
|
||||
return subclass
|
||||
end
|
||||
|
||||
return Class
|
||||
end
|
||||
|
||||
--luacheck: push no max line length
|
||||
|
||||
---@type (fun(name: string): table)|{ is_subclass: (fun(subclass: table, class: table): boolean), is_instance: (fun(instance: table, class: table): boolean) }
|
||||
local Object = setmetatable({
|
||||
is_subclass = is_subclass,
|
||||
is_instance = is_instance,
|
||||
}, {
|
||||
__call = create_object,
|
||||
})
|
||||
|
||||
--luacheck: pop
|
||||
|
||||
return Object
|
||||
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
|
||||
|
|
@ -0,0 +1,91 @@
|
|||
# Split
|
||||
|
||||
Split is can be used to split your current window or editor.
|
||||
|
||||
```lua
|
||||
local Split = require("nui.split")
|
||||
|
||||
local split = Split({
|
||||
relative = "editor",
|
||||
position = "bottom",
|
||||
size = "20%",
|
||||
})
|
||||
```
|
||||
|
||||
You can manipulate the associated buffer and window using the
|
||||
`split.bufnr` and `split.winid` properties.
|
||||
|
||||
## Options
|
||||
|
||||
### `ns_id`
|
||||
|
||||
**Type:** `number` or `string`
|
||||
|
||||
Namespace id (`number`) or name (`string`).
|
||||
|
||||
### `relative`
|
||||
|
||||
**Type:** `string` or `table`
|
||||
|
||||
This option affects how `size` is calculated.
|
||||
|
||||
**Examples**
|
||||
|
||||
Split current editor screen:
|
||||
|
||||
```lua
|
||||
relative = "editor"
|
||||
```
|
||||
|
||||
Split current window (_default_):
|
||||
|
||||
```lua
|
||||
relative = "win"
|
||||
```
|
||||
|
||||
Split window with specific id:
|
||||
|
||||
```lua
|
||||
relative = {
|
||||
type = "win",
|
||||
winid = 42,
|
||||
}
|
||||
```
|
||||
|
||||
### `position`
|
||||
|
||||
`position` can be one of: `"top"`, `"right"`, `"bottom"` or `"left"`.
|
||||
|
||||
### `size`
|
||||
|
||||
`size` can be `number` or `percentage string`.
|
||||
|
||||
For `percentage string`, size is calculated according to the option `relative`.
|
||||
|
||||
### `enter`
|
||||
|
||||
**Type:** `boolean`
|
||||
|
||||
If `false`, the split is not entered immediately after mount.
|
||||
|
||||
**Examples**
|
||||
|
||||
```lua
|
||||
enter = false
|
||||
```
|
||||
|
||||
### `buf_options`
|
||||
|
||||
Table containing buffer options to set for this split.
|
||||
|
||||
### `win_options`
|
||||
|
||||
Table containing window options to set for this split.
|
||||
|
||||
## Methods
|
||||
|
||||
[Methods from `nui.popup`](/lua/nui/popup#methods) are also available for `nui.split`.
|
||||
|
||||
## Wiki Page
|
||||
|
||||
You can find additional documentation/examples/guides/tips-n-tricks in [nui.split wiki page](https://github.com/MunifTanjim/nui.nvim/wiki/nui.split).
|
||||
378
.config/nvim/pack/tree/start/nui.nvim/lua/nui/split/init.lua
Normal file
378
.config/nvim/pack/tree/start/nui.nvim/lua/nui/split/init.lua
Normal file
|
|
@ -0,0 +1,378 @@
|
|||
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 split_utils = require("nui.split.utils")
|
||||
|
||||
local u = {
|
||||
clear_namespace = utils._.clear_namespace,
|
||||
get_next_id = utils._.get_next_id,
|
||||
normalize_namespace_id = utils._.normalize_namespace_id,
|
||||
split = split_utils,
|
||||
}
|
||||
|
||||
local split_direction_command_map = {
|
||||
editor = {
|
||||
top = "topleft",
|
||||
right = "vertical botright",
|
||||
bottom = "botright",
|
||||
left = "vertical topleft",
|
||||
},
|
||||
win = {
|
||||
top = "aboveleft",
|
||||
right = "vertical rightbelow",
|
||||
bottom = "belowright",
|
||||
left = "vertical leftabove",
|
||||
},
|
||||
}
|
||||
|
||||
---@param winid integer
|
||||
---@param win_config _nui_split_internal_win_config
|
||||
local function move_split_window(winid, win_config)
|
||||
if win_config.relative == "editor" then
|
||||
vim.api.nvim_win_call(winid, function()
|
||||
vim.cmd("wincmd " .. ({ top = "K", right = "L", bottom = "J", left = "H" })[win_config.position])
|
||||
end)
|
||||
elseif win_config.relative == "win" then
|
||||
local move_options = {
|
||||
vertical = win_config.position == "left" or win_config.position == "right",
|
||||
rightbelow = win_config.position == "bottom" or win_config.position == "right",
|
||||
}
|
||||
|
||||
vim.cmd(
|
||||
string.format(
|
||||
"noautocmd call win_splitmove(%s, %s, #{ vertical: %s, rightbelow: %s })",
|
||||
winid,
|
||||
win_config.win,
|
||||
move_options.vertical and 1 or 0,
|
||||
move_options.rightbelow and 1 or 0
|
||||
)
|
||||
)
|
||||
end
|
||||
end
|
||||
|
||||
---@param winid integer
|
||||
---@param win_config _nui_split_internal_win_config
|
||||
local function set_win_config(winid, win_config)
|
||||
if win_config.pending_changes.position then
|
||||
move_split_window(winid, win_config)
|
||||
end
|
||||
|
||||
if win_config.pending_changes.size then
|
||||
if win_config.width then
|
||||
vim.api.nvim_win_set_width(winid, win_config.width)
|
||||
elseif win_config.height then
|
||||
vim.api.nvim_win_set_height(winid, win_config.height)
|
||||
end
|
||||
end
|
||||
|
||||
win_config.pending_changes = {}
|
||||
end
|
||||
|
||||
--luacheck: push no max line length
|
||||
|
||||
---@alias nui_split_option_relative_type 'editor'|'win'
|
||||
---@alias nui_split_option_relative { type: nui_split_option_relative_type, winid?: number }
|
||||
|
||||
---@alias nui_split_option_position "'top'"|"'right'"|"'bottom'"|"'left'"
|
||||
|
||||
---@alias nui_split_option_size { height?: number|string }|{ width?: number|string }
|
||||
|
||||
---@alias _nui_split_internal_relative { type: nui_split_option_relative_type, win: number }
|
||||
---@alias _nui_split_internal_win_config { height?: number, width?: number, position: nui_split_option_position, relative: nui_split_option_relative, win?: integer, pending_changes: table<'position'|'size', boolean> }
|
||||
|
||||
--luacheck: pop
|
||||
|
||||
---@class nui_split_internal
|
||||
---@field enter? boolean
|
||||
---@field loading boolean
|
||||
---@field mounted boolean
|
||||
---@field buf_options table<string, any>
|
||||
---@field win_options table<string, any>
|
||||
---@field position nui_split_option_position
|
||||
---@field relative _nui_split_internal_relative
|
||||
---@field size { height?: number }|{ width?: number }
|
||||
---@field win_config _nui_split_internal_win_config
|
||||
---@field pending_quit? boolean
|
||||
---@field augroup table<'hide'|'unmount', string>
|
||||
|
||||
---@class nui_split_options
|
||||
---@field ns_id? string|integer
|
||||
---@field relative? nui_split_option_relative_type|nui_split_option_relative
|
||||
---@field position? nui_split_option_position
|
||||
---@field size? number|string|nui_split_option_size
|
||||
---@field enter? boolean
|
||||
---@field buf_options? table<string, any>
|
||||
---@field win_options? table<string, any>
|
||||
|
||||
---@class NuiSplit
|
||||
---@field private _ nui_split_internal
|
||||
---@field bufnr integer
|
||||
---@field ns_id integer
|
||||
---@field winid number
|
||||
local Split = Object("NuiSplit")
|
||||
|
||||
---@param options nui_split_options
|
||||
function Split:init(options)
|
||||
local id = u.get_next_id()
|
||||
|
||||
options = u.split.merge_default_options(options)
|
||||
options = u.split.normalize_options(options)
|
||||
|
||||
self._ = {
|
||||
id = id,
|
||||
enter = options.enter,
|
||||
buf_options = options.buf_options,
|
||||
loading = false,
|
||||
mounted = false,
|
||||
layout = {},
|
||||
position = options.position,
|
||||
size = {},
|
||||
win_options = options.win_options,
|
||||
win_config = {
|
||||
pending_changes = {},
|
||||
},
|
||||
augroup = {
|
||||
hide = string.format("%s_hide", id),
|
||||
unmount = string.format("%s_unmount", id),
|
||||
},
|
||||
}
|
||||
|
||||
self.ns_id = u.normalize_namespace_id(options.ns_id)
|
||||
|
||||
self:_buf_create()
|
||||
|
||||
self:update_layout(options)
|
||||
end
|
||||
|
||||
--luacheck: push no max line length
|
||||
|
||||
---@param config { relative?: nui_split_option_relative_type|nui_split_option_relative, position?: nui_split_option_position, size?: number|string|nui_split_option_size }
|
||||
function Split:update_layout(config)
|
||||
config = config or {}
|
||||
|
||||
u.split.update_layout_config(self._, config)
|
||||
|
||||
if self.winid then
|
||||
set_win_config(self.winid, self._.win_config)
|
||||
end
|
||||
end
|
||||
|
||||
--luacheck: pop
|
||||
|
||||
function Split:_open_window()
|
||||
if self.winid or not self.bufnr then
|
||||
return
|
||||
end
|
||||
|
||||
self.winid = vim.api.nvim_win_call(self._.relative.type == "editor" and 0 or self._.relative.win, function()
|
||||
vim.api.nvim_command(
|
||||
string.format(
|
||||
"silent noswapfile %s %ssplit",
|
||||
split_direction_command_map[self._.relative.type][self._.position],
|
||||
self._.size.width or self._.size.height or ""
|
||||
)
|
||||
)
|
||||
|
||||
return vim.api.nvim_get_current_win()
|
||||
end)
|
||||
|
||||
vim.api.nvim_win_set_buf(self.winid, self.bufnr)
|
||||
|
||||
if self._.enter then
|
||||
vim.api.nvim_set_current_win(self.winid)
|
||||
end
|
||||
|
||||
self._.win_config.pending_changes = { size = true }
|
||||
set_win_config(self.winid, self._.win_config)
|
||||
|
||||
utils._.set_win_options(self.winid, self._.win_options)
|
||||
end
|
||||
|
||||
function Split:_close_window()
|
||||
if not self.winid then
|
||||
return
|
||||
end
|
||||
|
||||
if vim.api.nvim_win_is_valid(self.winid) and not self._.pending_quit then
|
||||
vim.api.nvim_win_close(self.winid, true)
|
||||
end
|
||||
|
||||
self.winid = nil
|
||||
end
|
||||
|
||||
function Split:_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 Split:mount()
|
||||
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 = function()
|
||||
self._.pending_quit = true
|
||||
vim.schedule(function()
|
||||
self:unmount()
|
||||
self._.pending_quit = nil
|
||||
end)
|
||||
end,
|
||||
}, self.bufnr)
|
||||
autocmd.create("BufWinEnter", {
|
||||
group = self._.augroup.unmount,
|
||||
buffer = self.bufnr,
|
||||
callback = function()
|
||||
-- When two splits 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
|
||||
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:_buf_create()
|
||||
|
||||
utils._.set_buf_options(self.bufnr, self._.buf_options)
|
||||
|
||||
self:_open_window()
|
||||
|
||||
self._.loading = false
|
||||
self._.mounted = true
|
||||
end
|
||||
|
||||
function Split:hide()
|
||||
if self._.loading or not self._.mounted then
|
||||
return
|
||||
end
|
||||
|
||||
self._.loading = true
|
||||
|
||||
pcall(autocmd.delete_group, self._.augroup.hide)
|
||||
|
||||
self:_close_window()
|
||||
|
||||
self._.loading = false
|
||||
end
|
||||
|
||||
function Split: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:_open_window()
|
||||
|
||||
self._.loading = false
|
||||
end
|
||||
|
||||
function Split:_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._.pending_quit then
|
||||
vim.api.nvim_buf_delete(self.bufnr, { force = true })
|
||||
end
|
||||
end
|
||||
|
||||
buf_storage.cleanup(self.bufnr)
|
||||
|
||||
self.bufnr = nil
|
||||
end
|
||||
end
|
||||
|
||||
function Split: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:_buf_destroy()
|
||||
|
||||
self:_close_window()
|
||||
|
||||
self._.loading = false
|
||||
self._.mounted = false
|
||||
end
|
||||
|
||||
-- set keymap for this split
|
||||
---@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 Split:map(mode, key, handler, opts, ___force___)
|
||||
if not self.bufnr then
|
||||
error("split 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 Split:unmap(mode, key)
|
||||
if not self.bufnr then
|
||||
error("split buffer not found.")
|
||||
end
|
||||
|
||||
return keymap._del(self.bufnr, mode, key)
|
||||
end
|
||||
|
||||
---@param event string | string[]
|
||||
---@param handler string | function
|
||||
---@param options? table<"'once'" | "'nested'", boolean>
|
||||
function Split:on(event, handler, options)
|
||||
if not self.bufnr then
|
||||
error("split buffer not found.")
|
||||
end
|
||||
|
||||
autocmd.buf.define(self.bufnr, event, handler, options)
|
||||
end
|
||||
|
||||
---@param event? string | string[]
|
||||
function Split:off(event)
|
||||
if not self.bufnr then
|
||||
error("split buffer not found.")
|
||||
end
|
||||
|
||||
autocmd.buf.remove(self.bufnr, nil, event)
|
||||
end
|
||||
|
||||
---@alias NuiSplit.constructor fun(options: nui_split_options): NuiSplit
|
||||
---@type NuiSplit|NuiSplit.constructor
|
||||
local NuiSplit = Split
|
||||
|
||||
return NuiSplit
|
||||
177
.config/nvim/pack/tree/start/nui.nvim/lua/nui/split/utils.lua
Normal file
177
.config/nvim/pack/tree/start/nui.nvim/lua/nui/split/utils.lua
Normal file
|
|
@ -0,0 +1,177 @@
|
|||
local utils = require("nui.utils")
|
||||
local layout_utils = require("nui.layout.utils")
|
||||
|
||||
local u = {
|
||||
defaults = utils.defaults,
|
||||
get_editor_size = utils.get_editor_size,
|
||||
get_window_size = utils.get_window_size,
|
||||
is_type = utils.is_type,
|
||||
normalize_dimension = utils._.normalize_dimension,
|
||||
size = layout_utils.size,
|
||||
}
|
||||
|
||||
local mod = {}
|
||||
|
||||
---@param size number|string|nui_split_option_size
|
||||
---@param position nui_split_option_position
|
||||
---@return number|string size
|
||||
local function to_split_size(size, position)
|
||||
if not u.is_type("table", size) then
|
||||
---@cast size number|string
|
||||
return size
|
||||
end
|
||||
|
||||
if position == "left" or position == "right" then
|
||||
return size.width
|
||||
end
|
||||
|
||||
return size.height
|
||||
end
|
||||
|
||||
---@param options table
|
||||
---@return table options
|
||||
function mod.merge_default_options(options)
|
||||
options.relative = u.defaults(options.relative, "win")
|
||||
options.position = u.defaults(options.position, vim.go.splitbelow and "bottom" or "top")
|
||||
|
||||
options.enter = u.defaults(options.enter, true)
|
||||
|
||||
options.buf_options = u.defaults(options.buf_options, {})
|
||||
options.win_options = vim.tbl_extend("force", {
|
||||
winfixwidth = true,
|
||||
winfixheight = true,
|
||||
}, u.defaults(options.win_options, {}))
|
||||
|
||||
return options
|
||||
end
|
||||
|
||||
---@param options nui_split_options
|
||||
function mod.normalize_layout_options(options)
|
||||
if utils.is_type("string", options.relative) then
|
||||
options.relative = {
|
||||
---@diagnostic disable-next-line: assign-type-mismatch
|
||||
type = options.relative,
|
||||
}
|
||||
end
|
||||
|
||||
return options
|
||||
end
|
||||
|
||||
---@param options nui_split_options
|
||||
function mod.normalize_options(options)
|
||||
options = mod.normalize_layout_options(options)
|
||||
|
||||
return options
|
||||
end
|
||||
|
||||
local function parse_relative(relative, fallback_winid)
|
||||
local winid = u.defaults(relative.winid, fallback_winid)
|
||||
|
||||
return {
|
||||
type = relative.type,
|
||||
win = winid,
|
||||
}
|
||||
end
|
||||
|
||||
---@param relative _nui_split_internal_relative
|
||||
---@return { size: { height: integer, width: integer }, type: 'editor'|'window' }
|
||||
local function get_container_info(relative)
|
||||
if relative.type == "editor" then
|
||||
local size = u.get_editor_size()
|
||||
|
||||
-- best effort adjustments
|
||||
size.height = size.height - vim.api.nvim_get_option("cmdheight")
|
||||
if vim.api.nvim_get_option("laststatus") >= 2 then
|
||||
size.height = size.height - 1
|
||||
end
|
||||
if vim.api.nvim_get_option("showtabline") == 2 then
|
||||
size.height = size.height - 1
|
||||
end
|
||||
|
||||
return {
|
||||
size = size,
|
||||
type = "editor",
|
||||
}
|
||||
end
|
||||
|
||||
return {
|
||||
size = u.get_window_size(relative.win),
|
||||
type = "window",
|
||||
}
|
||||
end
|
||||
|
||||
---@param position nui_split_option_position
|
||||
---@param size number|string
|
||||
---@param container_size { width: number, height: number }
|
||||
---@return { width?: number, height?: number }
|
||||
function mod.calculate_window_size(position, size, container_size)
|
||||
if not size then
|
||||
return {}
|
||||
end
|
||||
|
||||
if position == "left" or position == "right" then
|
||||
return {
|
||||
width = u.normalize_dimension(size, container_size.width),
|
||||
}
|
||||
end
|
||||
|
||||
return {
|
||||
height = u.normalize_dimension(size, container_size.height),
|
||||
}
|
||||
end
|
||||
|
||||
function mod.update_layout_config(component_internal, config)
|
||||
local internal = component_internal
|
||||
|
||||
local options = mod.normalize_layout_options({
|
||||
relative = config.relative,
|
||||
position = config.position,
|
||||
size = config.size,
|
||||
})
|
||||
|
||||
if internal.relative and internal.relative.win and not vim.api.nvim_win_is_valid(internal.relative.win) then
|
||||
internal.relative.win = vim.api.nvim_get_current_win()
|
||||
|
||||
internal.win_config.win = internal.relative.win
|
||||
|
||||
internal.win_config.pending_changes.relative = true
|
||||
end
|
||||
|
||||
if options.relative then
|
||||
local fallback_winid = internal.relative and internal.relative.win or vim.api.nvim_get_current_win()
|
||||
internal.relative = parse_relative(options.relative, fallback_winid)
|
||||
|
||||
local prev_relative = internal.win_config.relative
|
||||
local prev_win = internal.win_config.win
|
||||
|
||||
internal.win_config.relative = internal.relative.type
|
||||
internal.win_config.win = internal.relative.type == "win" and internal.relative.win or nil
|
||||
|
||||
internal.win_config.pending_changes.relative = internal.win_config.relative ~= prev_relative
|
||||
or internal.win_config.win ~= prev_win
|
||||
end
|
||||
|
||||
if options.position or internal.win_config.pending_changes.relative then
|
||||
local prev_position = internal.win_config.position
|
||||
|
||||
internal.position = options.position or internal.position
|
||||
|
||||
internal.win_config.position = internal.position
|
||||
|
||||
internal.win_config.pending_changes.position = internal.win_config.position ~= prev_position
|
||||
end
|
||||
|
||||
if options.size or internal.win_config.pending_changes.position or internal.win_config.pending_changes.relative then
|
||||
internal.layout.size = to_split_size(options.size or internal.layout.size, internal.position)
|
||||
|
||||
internal.container_info = get_container_info(internal.relative)
|
||||
internal.size = mod.calculate_window_size(internal.position, internal.layout.size, internal.container_info.size)
|
||||
|
||||
internal.win_config.width = internal.size.width
|
||||
internal.win_config.height = internal.size.height
|
||||
|
||||
internal.win_config.pending_changes.size = true
|
||||
end
|
||||
end
|
||||
|
||||
return mod
|
||||
115
.config/nvim/pack/tree/start/nui.nvim/lua/nui/table/README.md
Normal file
115
.config/nvim/pack/tree/start/nui.nvim/lua/nui/table/README.md
Normal file
|
|
@ -0,0 +1,115 @@
|
|||
# NuiTable
|
||||
|
||||
NuiTable can render table-like structured content on the buffer.
|
||||
|
||||
**Examples**
|
||||
|
||||
```lua
|
||||
local NuiTable = require("nui.table")
|
||||
|
||||
local tbl = NuiTable({
|
||||
bufnr = bufnr,
|
||||
columns = {
|
||||
{
|
||||
align = "center",
|
||||
header = "Name",
|
||||
columns = {
|
||||
{ accessor_key = "firstName", header = "First" },
|
||||
{
|
||||
id = "lastName",
|
||||
accessor_fn = function(row)
|
||||
return row.lastName
|
||||
end,
|
||||
header = "Last",
|
||||
},
|
||||
},
|
||||
},
|
||||
{
|
||||
align = "right",
|
||||
accessor_key = "age",
|
||||
cell = function(cell)
|
||||
return Text(tostring(cell.get_value()), "DiagnosticInfo")
|
||||
end,
|
||||
header = "Age",
|
||||
},
|
||||
},
|
||||
data = {
|
||||
{ firstName = "John", lastName = "Doe", age = 42 },
|
||||
{ firstName = "Jane", lastName = "Doe", age = 27 },
|
||||
},
|
||||
})
|
||||
|
||||
tbl:render()
|
||||
```
|
||||
|
||||
## Options
|
||||
|
||||
### `bufnr`
|
||||
|
||||
**Type:** `number`
|
||||
|
||||
Id of the buffer where the table will be rendered.
|
||||
|
||||
---
|
||||
|
||||
### `ns_id`
|
||||
|
||||
**Type:** `number` or `string`
|
||||
|
||||
Namespace id (`number`) or name (`string`).
|
||||
|
||||
---
|
||||
|
||||
### `columns`
|
||||
|
||||
**Type:** `NuiTable.ColumnDef[]`
|
||||
|
||||
List of `NuiTable.ColumnDef` objects.
|
||||
|
||||
---
|
||||
|
||||
### `data`
|
||||
|
||||
**Type:** `any[]`
|
||||
|
||||
List of data items.
|
||||
|
||||
## Methods
|
||||
|
||||
### `tbl:get_cell`
|
||||
|
||||
_Signature:_ `tbl:get_cell(position?: {integer, integer}) -> NuiTable.Cell | nil`
|
||||
|
||||
**Parameters**
|
||||
|
||||
| Name | Type | Description |
|
||||
| ---------- | ---------------------- | ------------------------------------- |
|
||||
| `position` | `{ integer, integer }` | `(row, col)` tuple relative to cursor |
|
||||
|
||||
Returns the `NuiTable.Cell` if found.
|
||||
|
||||
### `tbl:refresh_cell`
|
||||
|
||||
_Signature:_ `tbl:refresh_cell(cell: NuiTable.Cell) -> nil`
|
||||
|
||||
Refreshes the `cell` on buffer.
|
||||
|
||||
**Parameters**
|
||||
|
||||
| Name | Type | Description |
|
||||
| ------ | --------------- | ----------- |
|
||||
| `cell` | `NuiTable.Cell` | cell |
|
||||
|
||||
### `tbl:render`
|
||||
|
||||
_Signature:_ `tbl:render(linenr_start?: integer) -> nil`
|
||||
|
||||
Renders the table on buffer.
|
||||
|
||||
| Name | Type | Description |
|
||||
| -------------- | ----------------- | ----------------------------- |
|
||||
| `linenr_start` | `integer` / `nil` | start line number (1-indexed) |
|
||||
|
||||
## Wiki Page
|
||||
|
||||
You can find additional documentation/examples/guides/tips-n-tricks in [nui.table wiki page](https://github.com/MunifTanjim/nui.nvim/wiki/nui.table).
|
||||
675
.config/nvim/pack/tree/start/nui.nvim/lua/nui/table/init.lua
Normal file
675
.config/nvim/pack/tree/start/nui.nvim/lua/nui/table/init.lua
Normal file
|
|
@ -0,0 +1,675 @@
|
|||
local Object = require("nui.object")
|
||||
local Text = require("nui.text")
|
||||
local Line = require("nui.line")
|
||||
local _ = require("nui.utils")._
|
||||
|
||||
-- luacheck: push no max comment line length
|
||||
|
||||
---@alias nui_table_border_char_name 'down_right'|'hor'|'down_hor'|'down_left'|'ver'|'ver_left'|'ver_hor'|'ver_left'|'up_right'|'up_hor'|'up_left'
|
||||
|
||||
---@alias _nui_table_header_kind
|
||||
---| -1 -- footer
|
||||
---| 1 -- header
|
||||
|
||||
---@class nui_t_list<T>: { [integer]: T, len: integer }
|
||||
|
||||
-- luacheck: pop
|
||||
|
||||
---@type table<nui_table_border_char_name,string>
|
||||
local default_border = {
|
||||
hor = "─",
|
||||
ver = "│",
|
||||
down_right = "┌",
|
||||
down_hor = "┬",
|
||||
down_left = "┐",
|
||||
ver_right = "├",
|
||||
ver_hor = "┼",
|
||||
ver_left = "┤",
|
||||
up_right = "└",
|
||||
up_hor = "┴",
|
||||
up_left = "┘",
|
||||
}
|
||||
|
||||
---@param internal nui_table_internal
|
||||
---@param columns NuiTable.ColumnDef[]
|
||||
---@param parent? NuiTable.ColumnDef
|
||||
---@param depth? integer
|
||||
local function prepare_columns(internal, columns, parent, depth)
|
||||
for _, col in ipairs(columns) do
|
||||
if col.header then
|
||||
internal.has_header = true
|
||||
end
|
||||
|
||||
if col.footer then
|
||||
internal.has_footer = true
|
||||
end
|
||||
|
||||
if not col.id then
|
||||
if col.accessor_key then
|
||||
col.id = col.accessor_key
|
||||
elseif type(col.header) == "string" then
|
||||
col.id = col.header --[[@as string]]
|
||||
elseif type(col.header) == "table" then
|
||||
col.id = (col.header --[[@as NuiText|NuiLine]]):content()
|
||||
end
|
||||
end
|
||||
|
||||
if not col.id then
|
||||
error("missing column id")
|
||||
end
|
||||
|
||||
if col.accessor_key and not col.accessor_fn then
|
||||
col.accessor_fn = function(row)
|
||||
return row[col.accessor_key]
|
||||
end
|
||||
end
|
||||
|
||||
col.depth = depth or 0
|
||||
col.parent = parent
|
||||
|
||||
if parent and not col.header then
|
||||
col.header = col.id
|
||||
internal.has_header = true
|
||||
end
|
||||
|
||||
if col.columns then
|
||||
prepare_columns(internal, col.columns, col, col.depth + 1)
|
||||
else
|
||||
table.insert(internal.columns, col)
|
||||
end
|
||||
|
||||
if col.depth == 0 then
|
||||
table.insert(internal.headers, col)
|
||||
else
|
||||
internal.headers.depth = math.max(internal.headers.depth, col.depth + 1)
|
||||
end
|
||||
|
||||
if not col.align then
|
||||
col.align = "left"
|
||||
end
|
||||
|
||||
if not col.width then
|
||||
col.width = 0
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
---@class NuiTable.ColumnDef
|
||||
---@field accessor_fn? fun(original_row: table, index: integer): string|NuiText|NuiLine
|
||||
---@field accessor_key? string
|
||||
---@field align? nui_t_text_align
|
||||
---@field cell? fun(info: NuiTable.Cell): string|NuiText|NuiLine
|
||||
---@field columns? NuiTable.ColumnDef[]
|
||||
---@field footer? string|NuiText|NuiLine|fun(info: { column: NuiTable.Column }): string|NuiText|NuiLine
|
||||
---@field header? string|NuiText|NuiLine|fun(info: { column: NuiTable.Column }): string|NuiText|NuiLine
|
||||
---@field id? string
|
||||
---@field max_width? integer
|
||||
---@field min_width? integer
|
||||
---@field width? integer
|
||||
|
||||
---@class NuiTable.Column
|
||||
---@field accessor_fn? fun(original_row: table, index: integer): string|NuiText|NuiLine
|
||||
---@field accessor_key? string
|
||||
---@field align nui_t_text_align
|
||||
---@field columns? NuiTable.ColumnDef[]
|
||||
---@field depth integer
|
||||
---@field id string
|
||||
---@field parent? NuiTable.Column
|
||||
---@field width integer
|
||||
|
||||
---@class NuiTable.Row
|
||||
---@field id string
|
||||
---@field index integer
|
||||
---@field original table
|
||||
|
||||
---@class NuiTable.Cell
|
||||
---@field column NuiTable.Column
|
||||
---@field content NuiText|NuiLine
|
||||
---@field get_value fun(): string|NuiText|NuiLine
|
||||
---@field row NuiTable.Row
|
||||
---@field range table<1|2|3|4, integer> -- [start_row, start_col, end_row, end_col]
|
||||
|
||||
---@class nui_table_internal
|
||||
---@field border table
|
||||
---@field buf_options table<string, any>
|
||||
---@field headers NuiTable.Column[]|{ depth: integer }
|
||||
---@field columns NuiTable.ColumnDef[]
|
||||
---@field data table[]
|
||||
---@field has_header boolean
|
||||
---@field has_footer boolean
|
||||
---@field linenr table<1|2, integer>
|
||||
---@field data_linenrs integer[]
|
||||
---@field data_grid nui_t_list<NuiTable.Cell[]>
|
||||
|
||||
---@class nui_table_options
|
||||
---@field bufnr integer
|
||||
---@field ns_id integer|string
|
||||
---@field columns NuiTable.ColumnDef[]
|
||||
---@field data table[]
|
||||
|
||||
---@class NuiTable
|
||||
---@field private _ nui_table_internal
|
||||
---@field bufnr integer
|
||||
---@field ns_id integer
|
||||
local Table = Object("NuiTable")
|
||||
|
||||
---@param options nui_table_options
|
||||
function Table:init(options)
|
||||
if options.bufnr then
|
||||
if not vim.api.nvim_buf_is_valid(options.bufnr) then
|
||||
error("invalid bufnr " .. options.bufnr)
|
||||
end
|
||||
|
||||
self.bufnr = options.bufnr
|
||||
end
|
||||
|
||||
if not self.bufnr then
|
||||
error("missing bufnr")
|
||||
end
|
||||
|
||||
self.ns_id = _.normalize_namespace_id(options.ns_id)
|
||||
|
||||
local border = vim.tbl_deep_extend("keep", options.border or {}, default_border)
|
||||
|
||||
self._ = {
|
||||
buf_options = vim.tbl_extend("force", {
|
||||
bufhidden = "hide",
|
||||
buflisted = false,
|
||||
buftype = "nofile",
|
||||
modifiable = false,
|
||||
readonly = true,
|
||||
swapfile = false,
|
||||
undolevels = 0,
|
||||
}, options.buf_options or {}),
|
||||
border = border,
|
||||
|
||||
headers = { depth = 1 },
|
||||
columns = {},
|
||||
data = options.data or {},
|
||||
|
||||
has_header = false,
|
||||
has_footer = false,
|
||||
|
||||
linenr = {},
|
||||
data_linenrs = {},
|
||||
}
|
||||
|
||||
prepare_columns(self._, options.columns or {})
|
||||
|
||||
_.set_buf_options(self.bufnr, self._.buf_options)
|
||||
end
|
||||
|
||||
---@param current_width integer
|
||||
---@param min_width? integer
|
||||
---@param max_width? integer
|
||||
---@param content_width integer
|
||||
local function get_col_width(current_width, min_width, max_width, content_width)
|
||||
local min = math.max(content_width, min_width or 0)
|
||||
return math.max(current_width, math.min(max_width or min, min))
|
||||
end
|
||||
|
||||
---@generic C: table
|
||||
---@param idx integer
|
||||
---@param grid nui_t_list<nui_t_list<C>>
|
||||
---@param kind _nui_table_header_kind
|
||||
---@return nui_t_list<C> header_row
|
||||
local function get_header_row_at(idx, grid, kind)
|
||||
local row = grid[idx]
|
||||
if not row then
|
||||
row = { len = 0 }
|
||||
grid[idx] = row
|
||||
grid.len = math.max(grid.len, kind * idx)
|
||||
end
|
||||
return row
|
||||
end
|
||||
|
||||
---@generic C: table
|
||||
---@param kind _nui_table_header_kind
|
||||
---@param columns (NuiTable.ColumnDef|{ depth: integer })[]
|
||||
---@param grid nui_t_list<nui_t_list<C>>
|
||||
---@param max_depth integer
|
||||
local function prepare_header_grid(kind, columns, grid, max_depth)
|
||||
local columns_len = #columns
|
||||
for column_idx = 1, columns_len do
|
||||
local column = columns[column_idx]
|
||||
|
||||
local row_idx = kind + kind * column.depth
|
||||
local row = get_header_row_at(row_idx, grid, kind)
|
||||
|
||||
local content = kind == 1 and column.header or kind == -1 and column.footer or Text("")
|
||||
if type(content) == "function" then
|
||||
--[[@cast column NuiTable.Column]]
|
||||
content = content({ column = column })
|
||||
--[[@cast content -function]]
|
||||
end
|
||||
if type(content) ~= "table" then
|
||||
content = Text(content --[[@as string]])
|
||||
--[[@cast content -string]]
|
||||
end
|
||||
|
||||
column.width = get_col_width(column.width, column.min_width, column.max_width, content:width())
|
||||
|
||||
local cell = {
|
||||
column = column,
|
||||
content = content,
|
||||
col_span = 1,
|
||||
row_span = 1,
|
||||
ridx = 1,
|
||||
}
|
||||
|
||||
row.len = row.len + 1
|
||||
row[row.len] = cell
|
||||
|
||||
if column.columns then
|
||||
cell.col_span = #column.columns
|
||||
prepare_header_grid(kind, column.columns, grid, max_depth)
|
||||
else
|
||||
cell.row_span = max_depth - column.depth
|
||||
for i = 1, cell.row_span - 1 do
|
||||
local span_row = get_header_row_at(row_idx + i * kind, grid, kind)
|
||||
span_row.len = span_row.len + 1
|
||||
span_row[span_row.len] = vim.tbl_extend("keep", { ridx = i + 1 }, cell)
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
---@param cell NuiTable.Cell
|
||||
---@return NuiText|NuiLine
|
||||
local function prepare_cell_content(cell)
|
||||
local column = cell.column --[[@as NuiTable.ColumnDef|NuiTable.Column]]
|
||||
local content = column.cell and column.cell(cell) or cell.get_value()
|
||||
if type(content) ~= "table" then
|
||||
content = Text(tostring(content))
|
||||
end
|
||||
return content
|
||||
end
|
||||
|
||||
---@return nui_t_list<NuiTable.Cell[]> data_grid
|
||||
---@return nui_t_list<nui_t_list<table>> header_grid
|
||||
function Table:_prepare_grid()
|
||||
---@type nui_t_list<NuiTable.Cell[]>
|
||||
local data_grid = {}
|
||||
|
||||
---@type nui_t_list<nui_t_list<table>>
|
||||
local header_grid = { len = 0 }
|
||||
if self._.has_header then
|
||||
prepare_header_grid(1, self._.headers, header_grid, self._.headers.depth)
|
||||
end
|
||||
|
||||
local rows = self._.data
|
||||
local rows_len = #rows
|
||||
|
||||
local columns = self._.columns
|
||||
local columns_len = #columns
|
||||
|
||||
for row_idx = 1, rows_len do
|
||||
local data = rows[row_idx]
|
||||
|
||||
data_grid[row_idx] = {}
|
||||
|
||||
---@type NuiTable.Row
|
||||
local row = {
|
||||
id = tostring(row_idx),
|
||||
index = row_idx,
|
||||
original = data,
|
||||
}
|
||||
|
||||
for column_idx = 1, columns_len do
|
||||
local column = columns[column_idx]
|
||||
|
||||
---@type NuiTable.Cell
|
||||
local cell = {
|
||||
row = row,
|
||||
column = column,
|
||||
get_value = function()
|
||||
return column.accessor_fn(row.original, row.index)
|
||||
end,
|
||||
}
|
||||
|
||||
cell.content = prepare_cell_content(cell)
|
||||
|
||||
column.width = get_col_width(column.width, column.min_width, column.max_width, cell.content:width())
|
||||
|
||||
data_grid[row_idx][column_idx] = cell
|
||||
end
|
||||
end
|
||||
|
||||
if self._.has_footer then
|
||||
prepare_header_grid(-1, self._.headers, header_grid, self._.headers.depth)
|
||||
end
|
||||
|
||||
for idx = -header_grid.len, header_grid.len do
|
||||
for _, th in ipairs(header_grid[idx] or {}) do
|
||||
local column = th.column
|
||||
if column.columns then
|
||||
column.width = 0
|
||||
for i = 1, th.col_span do
|
||||
column.width = column.width + column.columns[i].width
|
||||
end
|
||||
column.width = column.width + th.col_span - 1
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
data_grid.len = rows_len
|
||||
|
||||
return data_grid, header_grid
|
||||
end
|
||||
|
||||
---@param line NuiLine
|
||||
---@param content NuiLine|NuiText
|
||||
---@param width integer
|
||||
---@param align nui_t_text_align
|
||||
local function append_content(line, content, width, align)
|
||||
if content._texts then
|
||||
--[[@cast content NuiLine]]
|
||||
_.truncate_nui_line(content, width)
|
||||
else
|
||||
--[[@cast content NuiText]]
|
||||
_.truncate_nui_text(content, width)
|
||||
end
|
||||
local left_gap_width, right_gap_width = _.calculate_gap_width(align, width, content:width())
|
||||
if left_gap_width > 0 then
|
||||
line:append(Text(string.rep(" ", left_gap_width)))
|
||||
end
|
||||
line:append(content)
|
||||
if right_gap_width > 0 then
|
||||
line:append(Text(string.rep(" ", right_gap_width)))
|
||||
end
|
||||
return line
|
||||
end
|
||||
|
||||
---@param kind _nui_table_header_kind
|
||||
---@param lines nui_t_list<NuiLine>
|
||||
---@param grid nui_t_list<nui_t_list<table>>
|
||||
function Table:_prepare_header_lines(kind, lines, grid)
|
||||
local line_idx = lines.len
|
||||
|
||||
local start_idx, end_idx = 1, grid.len
|
||||
if kind == -1 then
|
||||
start_idx, end_idx = -grid.len, -1
|
||||
end
|
||||
|
||||
local border = self._.border
|
||||
|
||||
for row_idx = start_idx, end_idx do
|
||||
local row = grid[row_idx]
|
||||
if not row then
|
||||
break
|
||||
end
|
||||
|
||||
local inner_border_line = Line()
|
||||
local data_line = Line()
|
||||
local outer_border_line = Line()
|
||||
|
||||
outer_border_line:append(kind == 1 and border.down_right or border.up_right)
|
||||
|
||||
data_line:append(border.ver)
|
||||
|
||||
local cells_len = #row
|
||||
for cell_idx = 1, cells_len do
|
||||
local prev_cell = row[cell_idx - 1]
|
||||
local cell = row[cell_idx]
|
||||
local next_cell = row[cell_idx + 1]
|
||||
|
||||
if cell.row_span == cell.ridx then
|
||||
if cell_idx == 1 or (prev_cell and prev_cell.ridx ~= prev_cell.row_span) then
|
||||
inner_border_line:append(border.ver_right)
|
||||
else
|
||||
inner_border_line:append(border.ver_hor)
|
||||
end
|
||||
elseif next_cell then
|
||||
inner_border_line:append(border.ver)
|
||||
else
|
||||
inner_border_line:append(border.ver_left)
|
||||
end
|
||||
|
||||
local column = cell.column
|
||||
|
||||
if column.columns then
|
||||
for sc_idx = 1, cell.col_span do
|
||||
local sub_column = column.columns[sc_idx]
|
||||
inner_border_line:append(string.rep(border.hor, sub_column.width))
|
||||
if sc_idx ~= cell.col_span then
|
||||
inner_border_line:append(kind == 1 and border.down_hor or border.up_hor)
|
||||
end
|
||||
end
|
||||
else
|
||||
if cell.ridx == cell.row_span then
|
||||
inner_border_line:append(string.rep(border.hor, column.width))
|
||||
else
|
||||
inner_border_line:append(string.rep(" ", column.width))
|
||||
end
|
||||
end
|
||||
|
||||
if cell.ridx == cell.row_span then
|
||||
append_content(data_line, cell.content, column.width, column.align)
|
||||
else
|
||||
append_content(data_line, Text(""), column.width, column.align)
|
||||
end
|
||||
data_line:append(border.ver)
|
||||
|
||||
outer_border_line:append(string.rep(border.hor, column.width))
|
||||
outer_border_line:append(kind == 1 and border.down_hor or border.up_hor)
|
||||
end
|
||||
|
||||
local last_cell = row[cells_len]
|
||||
if last_cell.ridx == last_cell.row_span then
|
||||
inner_border_line:append(border.ver_left)
|
||||
else
|
||||
inner_border_line:append(border.ver)
|
||||
end
|
||||
|
||||
outer_border_line._texts[#outer_border_line._texts]:set(kind == 1 and border.down_left or border.up_left)
|
||||
|
||||
if kind == -1 then
|
||||
line_idx = line_idx + 1
|
||||
lines[line_idx] = inner_border_line
|
||||
elseif row_idx == 1 then
|
||||
line_idx = line_idx + 1
|
||||
lines[line_idx] = outer_border_line
|
||||
end
|
||||
line_idx = line_idx + 1
|
||||
lines[line_idx] = data_line
|
||||
if kind == 1 then
|
||||
line_idx = line_idx + 1
|
||||
lines[line_idx] = inner_border_line
|
||||
elseif row_idx == -1 then
|
||||
line_idx = line_idx + 1
|
||||
lines[line_idx] = outer_border_line
|
||||
end
|
||||
end
|
||||
|
||||
lines.len = line_idx
|
||||
end
|
||||
|
||||
---@param linenr_start? integer start line number (1-indexed)
|
||||
function Table:render(linenr_start)
|
||||
if #self._.columns == 0 then
|
||||
return
|
||||
end
|
||||
|
||||
linenr_start = math.max(1, linenr_start or self._.linenr[1] or 1)
|
||||
local prev_linenr = { self._.linenr[1], self._.linenr[2] }
|
||||
|
||||
local data_grid, header_grid = self:_prepare_grid()
|
||||
|
||||
self._.data_grid = data_grid
|
||||
|
||||
local line_idx = 0
|
||||
---@type nui_t_list<NuiLine>
|
||||
local lines = { len = line_idx }
|
||||
|
||||
self:_prepare_header_lines(1, lines, header_grid)
|
||||
line_idx = lines.len
|
||||
|
||||
local border = self._.border
|
||||
|
||||
local rows_len = data_grid.len
|
||||
|
||||
if line_idx == 0 and rows_len > 0 then
|
||||
local columns = self._.columns
|
||||
local columns_len = #columns
|
||||
|
||||
local top_border_line = Line()
|
||||
|
||||
top_border_line:append(border.down_right)
|
||||
for column_idx = 1, columns_len do
|
||||
local column = columns[column_idx]
|
||||
top_border_line:append(string.rep(border.hor, column.width))
|
||||
if column_idx ~= columns_len then
|
||||
top_border_line:append(border.down_hor)
|
||||
end
|
||||
end
|
||||
top_border_line:append(border.down_left)
|
||||
|
||||
line_idx = line_idx + 1
|
||||
lines[line_idx] = top_border_line
|
||||
end
|
||||
|
||||
local data_linenrs = self._.data_linenrs
|
||||
|
||||
for row_idx = 1, rows_len do
|
||||
local char_idx = 0
|
||||
|
||||
local is_last_line = row_idx == rows_len
|
||||
local bottom_border_mid = is_last_line and border.up_hor or border.ver_hor
|
||||
|
||||
local row = data_grid[row_idx]
|
||||
|
||||
local data_line = Line()
|
||||
local bottom_border_line = Line()
|
||||
|
||||
local data_linenr = line_idx + linenr_start
|
||||
data_line:append(border.ver)
|
||||
char_idx = char_idx + 1
|
||||
|
||||
bottom_border_line:append(is_last_line and border.up_right or border.ver_right)
|
||||
|
||||
local cells_len = #row
|
||||
for cell_idx = 1, cells_len do
|
||||
local cell = row[cell_idx]
|
||||
|
||||
local column = cell.column
|
||||
|
||||
append_content(data_line, cell.content, column.width, column.align)
|
||||
data_line:append(border.ver)
|
||||
cell.range = { data_linenr, char_idx, data_linenr, char_idx + column.width }
|
||||
char_idx = cell.range[4] + 1
|
||||
|
||||
bottom_border_line:append(string.rep(border.hor, column.width))
|
||||
bottom_border_line:append(bottom_border_mid)
|
||||
end
|
||||
bottom_border_line._texts[#bottom_border_line._texts]:set(is_last_line and border.up_left or border.ver_left)
|
||||
|
||||
line_idx = line_idx + 1
|
||||
lines[line_idx] = data_line
|
||||
|
||||
data_linenrs[row_idx] = data_linenr
|
||||
|
||||
if not is_last_line or not header_grid[-1] then
|
||||
line_idx = line_idx + 1
|
||||
lines[line_idx] = bottom_border_line
|
||||
end
|
||||
end
|
||||
|
||||
lines.len = line_idx
|
||||
self:_prepare_header_lines(-1, lines, header_grid)
|
||||
line_idx = lines.len
|
||||
lines.len = nil
|
||||
|
||||
_.set_buf_options(self.bufnr, { modifiable = true, readonly = false })
|
||||
|
||||
_.clear_namespace(self.bufnr, self.ns_id)
|
||||
|
||||
-- if linenr_start was shifted downwards,
|
||||
-- clear the previously rendered lines above.
|
||||
_.clear_lines(
|
||||
self.bufnr,
|
||||
math.min(linenr_start, prev_linenr[1] or linenr_start),
|
||||
prev_linenr[1] and linenr_start - 1 or 0
|
||||
)
|
||||
|
||||
-- for initial render, start inserting in a single line.
|
||||
-- for subsequent renders, replace the lines from previous render.
|
||||
_.render_lines(lines, self.bufnr, self.ns_id, linenr_start, prev_linenr[1] and prev_linenr[2] or linenr_start)
|
||||
|
||||
_.set_buf_options(self.bufnr, { modifiable = false, readonly = true })
|
||||
|
||||
self._.linenr[1], self._.linenr[2] = linenr_start, line_idx + linenr_start - 1
|
||||
end
|
||||
|
||||
---@param position? {[1]: integer, [2]: integer}
|
||||
function Table:get_cell(position)
|
||||
local pos = vim.fn.getcharpos(".") --[[@as integer[] ]]
|
||||
local line, char = pos[2], pos[3]
|
||||
|
||||
local row_idx = 0
|
||||
for idx, linenr in ipairs(self._.data_linenrs) do
|
||||
if linenr == line then
|
||||
row_idx = idx
|
||||
break
|
||||
elseif linenr > line then
|
||||
break
|
||||
end
|
||||
end
|
||||
row_idx = row_idx + (position and position[1] or 0)
|
||||
|
||||
local row = self._.data_grid[row_idx]
|
||||
if not row then
|
||||
return
|
||||
end
|
||||
|
||||
local cell_idx = 0
|
||||
for idx, cell in ipairs(row) do
|
||||
local range = cell.range
|
||||
if range[2] < char and char <= range[4] then
|
||||
cell_idx = idx
|
||||
end
|
||||
end
|
||||
cell_idx = cell_idx + (position and position[2] or 0)
|
||||
|
||||
return row[cell_idx]
|
||||
end
|
||||
|
||||
function Table:refresh_cell(cell)
|
||||
local column = cell.column
|
||||
|
||||
local range = cell.range
|
||||
local byte_range = _.char_to_byte_range(self.bufnr, range[1], range[2], range[4])
|
||||
|
||||
local content = prepare_cell_content(cell)
|
||||
if cell.content ~= content then
|
||||
cell.content = content
|
||||
|
||||
local extmarks = vim.api.nvim_buf_get_extmarks(
|
||||
self.bufnr,
|
||||
self.ns_id,
|
||||
{ range[1] - 1, byte_range[1] },
|
||||
{ range[3] - 1, byte_range[2] - 1 },
|
||||
{}
|
||||
)
|
||||
for _, extmark in ipairs(extmarks) do
|
||||
vim.api.nvim_buf_del_extmark(self.bufnr, self.ns_id, extmark[1])
|
||||
end
|
||||
end
|
||||
|
||||
_.set_buf_options(self.bufnr, { modifiable = true, readonly = false })
|
||||
_.render_lines(
|
||||
{ append_content(Line(), content, column.width, column.align) },
|
||||
self.bufnr,
|
||||
self.ns_id,
|
||||
range[1],
|
||||
range[3],
|
||||
byte_range[1],
|
||||
byte_range[2]
|
||||
)
|
||||
_.set_buf_options(self.bufnr, { modifiable = false, readonly = true })
|
||||
end
|
||||
|
||||
---@alias NuiTable.constructor fun(options: nui_table_options): NuiTable
|
||||
---@type NuiTable|NuiTable.constructor
|
||||
local NuiTable = Table
|
||||
|
||||
return NuiTable
|
||||
141
.config/nvim/pack/tree/start/nui.nvim/lua/nui/text/README.md
Normal file
141
.config/nvim/pack/tree/start/nui.nvim/lua/nui/text/README.md
Normal file
|
|
@ -0,0 +1,141 @@
|
|||
# NuiText
|
||||
|
||||
NuiText is an abstraction layer on top of the following native functions:
|
||||
|
||||
- `vim.api.nvim_buf_set_text` (check `:h nvim_buf_set_text()`)
|
||||
- `vim.api.nvim_buf_set_extmark` (check `:h nvim_buf_set_extmark()`)
|
||||
|
||||
It helps you set text and add highlight for it on the buffer.
|
||||
|
||||
_Signature:_ `NuiText(content, extmark?)`
|
||||
|
||||
**Examples**
|
||||
|
||||
```lua
|
||||
local NuiText = require("nui.text")
|
||||
|
||||
local text = NuiText("Something Went Wrong!", "Error")
|
||||
|
||||
local bufnr, ns_id, linenr_start, byte_start = 0, -1, 1, 0
|
||||
|
||||
text:render(bufnr, ns_id, linenr_start, byte_start)
|
||||
```
|
||||
|
||||
## Parameters
|
||||
|
||||
### `content`
|
||||
|
||||
**Type:** `string` or `table`
|
||||
|
||||
Text content or `NuiText` object.
|
||||
|
||||
If `NuiText` object is passed, a copy of it is created.
|
||||
|
||||
### `extmark`
|
||||
|
||||
**Type:** `string` or `table`
|
||||
|
||||
Highlight group name or extmark options.
|
||||
|
||||
If a `string` is passed, it is used as the highlight group name.
|
||||
|
||||
If a `table` is passed it is used as extmark data. It can have the
|
||||
following keys:
|
||||
|
||||
| Key | Description |
|
||||
| ------------ | -------------------- |
|
||||
| `"hl_group"` | highlight group name |
|
||||
|
||||
For more, check `:help nvim_buf_set_extmark()`.
|
||||
|
||||
## Methods
|
||||
|
||||
### `text:set`
|
||||
|
||||
_Signature:_ `text:set(content, extmark?)`
|
||||
|
||||
Sets the text content and highlight information.
|
||||
|
||||
**Parameters**
|
||||
|
||||
| Name | Type | Description |
|
||||
| --------- | ------------------- | --------------------------------------- |
|
||||
| `content` | `string` | text content |
|
||||
| `extmark` | `string` or `table` | highlight group name or extmark options |
|
||||
|
||||
This `extmark` parameter is exactly the same as `NuiText`'s `extmark` parameter.
|
||||
|
||||
### `text:content`
|
||||
|
||||
_Signature:_ `text:content()`
|
||||
|
||||
Returns the text content.
|
||||
|
||||
### `text:length`
|
||||
|
||||
_Signature:_ `text:length()`
|
||||
|
||||
Returns the byte length of the text.
|
||||
|
||||
### `text:width`
|
||||
|
||||
_Signature:_ `text:width()`
|
||||
|
||||
Returns the character length of the text.
|
||||
|
||||
### `text:highlight`
|
||||
|
||||
_Signature:_ `text:highlight(bufnr, ns_id, linenr, byte_start)`
|
||||
|
||||
Applies highlight for the text.
|
||||
|
||||
**Parameters**
|
||||
|
||||
| Name | Type | Description |
|
||||
| ------------ | -------- | -------------------------------------------------- |
|
||||
| `bufnr` | `number` | buffer number |
|
||||
| `ns_id` | `number` | namespace id (use `-1` for fallback namespace) |
|
||||
| `linenr` | `number` | line number (1-indexed) |
|
||||
| `byte_start` | `number` | start position of the text on the line (0-indexed) |
|
||||
|
||||
### `text:render`
|
||||
|
||||
_Signature:_ `text:render(bufnr, ns_id, linenr_start, byte_start, linenr_end?, byte_end?)`
|
||||
|
||||
Sets the text on buffer and applies highlight.
|
||||
|
||||
**Parameters**
|
||||
|
||||
| Name | Type | Description |
|
||||
| -------------- | -------- | -------------------------------------------------- |
|
||||
| `bufnr` | `number` | buffer number |
|
||||
| `ns_id` | `number` | namespace id (use `-1` for fallback namespace) |
|
||||
| `linenr_start` | `number` | start line number (1-indexed) |
|
||||
| `byte_start` | `number` | start position of the text on the line (0-indexed) |
|
||||
| `linenr_end` | `number` | end line number (1-indexed) |
|
||||
| `byte_end` | `number` | end position of the text on the line (0-indexed) |
|
||||
|
||||
### `text:render_char`
|
||||
|
||||
_Signature:_ `text:render_char(bufnr, ns_id, linenr_start, char_start, linenr_end?, char_end?)`
|
||||
|
||||
Sets the text on buffer and applies highlight.
|
||||
|
||||
This does the thing as `text:render` method, but you can use character count
|
||||
instead of byte count. It will convert multibyte character count to appropriate
|
||||
byte count for you.
|
||||
|
||||
**Parameters**
|
||||
|
||||
| Name | Type | Description |
|
||||
| -------------- | -------- | -------------------------------------------------- |
|
||||
| `bufnr` | `number` | buffer number |
|
||||
| `ns_id` | `number` | namespace id (use `-1` for fallback namespace) |
|
||||
| `linenr_start` | `number` | start line number (1-indexed) |
|
||||
| `char_start` | `number` | start position of the text on the line (0-indexed) |
|
||||
| `linenr_end` | `number` | end line number (1-indexed) |
|
||||
| `char_end` | `number` | end position of the text on the line (0-indexed) |
|
||||
|
||||
## Wiki Page
|
||||
|
||||
You can find additional documentation/examples/guides/tips-n-tricks in [nui.text wiki page](https://github.com/MunifTanjim/nui.nvim/wiki/nui.text).
|
||||
114
.config/nvim/pack/tree/start/nui.nvim/lua/nui/text/init.lua
Normal file
114
.config/nvim/pack/tree/start/nui.nvim/lua/nui/text/init.lua
Normal file
|
|
@ -0,0 +1,114 @@
|
|||
local Object = require("nui.object")
|
||||
local _ = require("nui.utils")._
|
||||
local is_type = require("nui.utils").is_type
|
||||
|
||||
---@class nui_text_extmark
|
||||
---@field id? integer
|
||||
---@field hl_group? string
|
||||
---@field [string] any
|
||||
|
||||
---@class NuiText
|
||||
---@field protected extmark? nui_text_extmark
|
||||
local Text = Object("NuiText")
|
||||
|
||||
---@param content string|NuiText text content or NuiText object
|
||||
---@param extmark? string|nui_text_extmark highlight group name or extmark options
|
||||
function Text:init(content, extmark)
|
||||
if type(content) == "string" then
|
||||
self:set(content, extmark)
|
||||
else
|
||||
-- cloning
|
||||
self:set(content._content, extmark or content.extmark)
|
||||
end
|
||||
end
|
||||
|
||||
---@param content string text content
|
||||
---@param extmark? string|nui_text_extmark highlight group name or extmark options
|
||||
---@return NuiText
|
||||
function Text:set(content, extmark)
|
||||
if self._content ~= content then
|
||||
self._content = content
|
||||
self._length = vim.fn.strlen(content)
|
||||
self._width = vim.api.nvim_strwidth(content)
|
||||
end
|
||||
|
||||
if extmark then
|
||||
-- preserve self.extmark.id
|
||||
local id = self.extmark and self.extmark.id or nil
|
||||
self.extmark = is_type("string", extmark) and { hl_group = extmark } or vim.deepcopy(extmark)
|
||||
self.extmark.id = id
|
||||
end
|
||||
|
||||
return self
|
||||
end
|
||||
|
||||
---@return string
|
||||
function Text:content()
|
||||
return self._content
|
||||
end
|
||||
|
||||
---@return number
|
||||
function Text:length()
|
||||
return self._length
|
||||
end
|
||||
|
||||
---@return number
|
||||
function Text:width()
|
||||
return self._width
|
||||
end
|
||||
|
||||
---@param bufnr number buffer number
|
||||
---@param ns_id number namespace id
|
||||
---@param linenr number line number (1-indexed)
|
||||
---@param byte_start number start byte position (0-indexed)
|
||||
---@return nil
|
||||
function Text:highlight(bufnr, ns_id, linenr, byte_start)
|
||||
if not self.extmark then
|
||||
return
|
||||
end
|
||||
|
||||
self.extmark.end_col = byte_start + self:length()
|
||||
|
||||
self.extmark.id =
|
||||
vim.api.nvim_buf_set_extmark(bufnr, _.ensure_namespace_id(ns_id), linenr - 1, byte_start, self.extmark)
|
||||
end
|
||||
|
||||
---@param bufnr number buffer number
|
||||
---@param ns_id number namespace id
|
||||
---@param linenr_start number start line number (1-indexed)
|
||||
---@param byte_start number start byte position (0-indexed)
|
||||
---@param linenr_end? number end line number (1-indexed)
|
||||
---@param byte_end? number end byte position (0-indexed)
|
||||
---@return nil
|
||||
function Text:render(bufnr, ns_id, linenr_start, byte_start, linenr_end, byte_end)
|
||||
local row_start = linenr_start - 1
|
||||
local row_end = linenr_end and linenr_end - 1 or row_start
|
||||
|
||||
local col_start = byte_start
|
||||
local col_end = byte_end or byte_start + self:length()
|
||||
|
||||
local content = self:content()
|
||||
|
||||
vim.api.nvim_buf_set_text(bufnr, row_start, col_start, row_end, col_end, { content })
|
||||
|
||||
self:highlight(bufnr, ns_id, linenr_start, byte_start)
|
||||
end
|
||||
|
||||
---@param bufnr number buffer number
|
||||
---@param ns_id number namespace id
|
||||
---@param linenr_start number start line number (1-indexed)
|
||||
---@param char_start number start character position (0-indexed)
|
||||
---@param linenr_end? number end line number (1-indexed)
|
||||
---@param char_end? number end character position (0-indexed)
|
||||
---@return nil
|
||||
function Text:render_char(bufnr, ns_id, linenr_start, char_start, linenr_end, char_end)
|
||||
char_end = char_end or char_start + self:width()
|
||||
local byte_range = _.char_to_byte_range(bufnr, linenr_start, char_start, char_end)
|
||||
self:render(bufnr, ns_id, linenr_start, byte_range[1], linenr_end, byte_range[2])
|
||||
end
|
||||
|
||||
---@alias NuiText.constructor fun(content: string|NuiText, extmark?: string|nui_text_extmark): NuiText
|
||||
---@type NuiText|NuiText.constructor
|
||||
local NuiText = Text
|
||||
|
||||
return NuiText
|
||||
308
.config/nvim/pack/tree/start/nui.nvim/lua/nui/tree/README.md
Normal file
308
.config/nvim/pack/tree/start/nui.nvim/lua/nui/tree/README.md
Normal file
|
|
@ -0,0 +1,308 @@
|
|||
# NuiTree
|
||||
|
||||
NuiTree can render tree-like structured content on the buffer.
|
||||
|
||||
**Examples**
|
||||
|
||||
```lua
|
||||
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()
|
||||
```
|
||||
|
||||
## Options
|
||||
|
||||
### `bufnr`
|
||||
|
||||
**Type:** `number`
|
||||
|
||||
Id of the buffer where the tree will be rendered.
|
||||
|
||||
---
|
||||
|
||||
### `ns_id`
|
||||
|
||||
**Type:** `number` or `string`
|
||||
|
||||
Namespace id (`number`) or name (`string`).
|
||||
|
||||
---
|
||||
|
||||
### `nodes`
|
||||
|
||||
**Type:** `table`
|
||||
|
||||
List of [`NuiTree.Node`](#nuitreenode) objects.
|
||||
|
||||
---
|
||||
|
||||
### `get_node_id`
|
||||
|
||||
**Type:** `function`
|
||||
|
||||
_Signature:_ `get_node_id(node) -> string`
|
||||
|
||||
If provided, this function is used for generating node's id.
|
||||
|
||||
The return value should be a unique `string`.
|
||||
|
||||
**Example**
|
||||
|
||||
```lua
|
||||
get_node_id = function(node)
|
||||
if node.id then
|
||||
return "-" .. node.id
|
||||
end
|
||||
|
||||
if node.text then
|
||||
return string.format("%s-%s-%s", node:get_parent_id() or "", node:get_depth(), node.text)
|
||||
end
|
||||
|
||||
return "-" .. math.random()
|
||||
end,
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
### `prepare_node`
|
||||
|
||||
**Type:** `function`
|
||||
|
||||
_Signature:_ `prepare_node(node, parent_node?) -> nil | string | string[] | NuiLine | NuiLine[]`
|
||||
|
||||
If provided, this function is used for preparing each node line.
|
||||
|
||||
The return value should be a `NuiLine` object or `string` or a list containing either of them.
|
||||
|
||||
If return value is `nil`, that node will not be rendered.
|
||||
|
||||
**Example**
|
||||
|
||||
```lua
|
||||
prepare_node = function(node)
|
||||
local line = NuiLine()
|
||||
|
||||
line:append(string.rep(" ", node:get_depth() - 1))
|
||||
|
||||
if node:has_children() then
|
||||
line:append(node:is_expanded() and " " or " ")
|
||||
else
|
||||
line:append(" ")
|
||||
end
|
||||
|
||||
line:append(node.text)
|
||||
|
||||
return line
|
||||
end,
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
### `buf_options`
|
||||
|
||||
**Type:** `table`
|
||||
|
||||
Contains all buffer related options (check `:h options | /local to buffer`).
|
||||
|
||||
**Examples**
|
||||
|
||||
```lua
|
||||
buf_options = {
|
||||
bufhidden = "hide",
|
||||
buflisted = false,
|
||||
buftype = "nofile",
|
||||
swapfile = false,
|
||||
},
|
||||
```
|
||||
|
||||
## Methods
|
||||
|
||||
### `tree:get_node`
|
||||
|
||||
_Signature:_ `tree:get_node(node_id_or_linenr?) -> NuiTreeNode | nil, number | nil, number | nil`
|
||||
|
||||
**Parameters**
|
||||
|
||||
| Name | Type | Description |
|
||||
| ------------------- | ----------------------------- | ------------------------ |
|
||||
| `node_id_or_linenr` | `number` or `string` or `nil` | node's id or line number |
|
||||
|
||||
If `node_id_or_linenr` is `string`, the node with that _id_ is returned.
|
||||
|
||||
If `node_id_or_linenr` is `number`, the node on that _linenr_ is returned.
|
||||
|
||||
If `node_id` is `nil`, the current node under cursor is returned.
|
||||
|
||||
Returns the `node` if found, and the start and end `linenr` if it is rendered.
|
||||
|
||||
### `tree:get_nodes`
|
||||
|
||||
_Signature:_ `tree:get_node(parent_id?) -> NuiTreeNode[]`
|
||||
|
||||
**Parameters**
|
||||
|
||||
| Name | Type | Description |
|
||||
| ----------- | ----------------- | ---------------- |
|
||||
| `parent_id` | `string` or `nil` | parent node's id |
|
||||
|
||||
If `parent_id` is present, child nodes under that parent are returned,
|
||||
Otherwise root nodes are returned.
|
||||
|
||||
### `tree:add_node`
|
||||
|
||||
_Signature:_ `tree:add_node(node, parent_id?)`
|
||||
|
||||
Adds a node to the tree.
|
||||
|
||||
| Name | Type | Description |
|
||||
| ----------- | ----------------- | ---------------- |
|
||||
| `node` | `NuiTree.Node` | node |
|
||||
| `parent_id` | `string` or `nil` | parent node's id |
|
||||
|
||||
If `parent_id` is present, node is added under that parent,
|
||||
Otherwise node is added to the tree root.
|
||||
|
||||
### `tree:remove_node`
|
||||
|
||||
_Signature:_ `tree:remove_node(node)`
|
||||
|
||||
Removes a node from the tree.
|
||||
|
||||
Returns the removed node.
|
||||
|
||||
| Name | Type | Description |
|
||||
| --------- | -------- | ----------- |
|
||||
| `node_id` | `string` | node's id |
|
||||
|
||||
### `tree:set_nodes`
|
||||
|
||||
_Signature:_ `tree:set_nodes(nodes, parent_id?)`
|
||||
|
||||
Adds a node to the tree.
|
||||
|
||||
| Name | Type | Description |
|
||||
| ----------- | ----------------- | ---------------- |
|
||||
| `nodes` | `NuiTree.Node[]` | list of nodes |
|
||||
| `parent_id` | `string` or `nil` | parent node's id |
|
||||
|
||||
If `parent_id` is present, nodes are set as parent node's children,
|
||||
otherwise nodes are set at tree root.
|
||||
|
||||
### `tree:render`
|
||||
|
||||
_Signature:_ `tree:render(linenr_start?)`
|
||||
|
||||
Renders the tree on buffer.
|
||||
|
||||
| Name | Type | Description |
|
||||
| -------------- | ---------------- | ----------------------------- |
|
||||
| `linenr_start` | `number` / `nil` | start line number (1-indexed) |
|
||||
|
||||
## NuiTree.Node
|
||||
|
||||
`NuiTree.Node` is used to create a node object for `NuiTree`.
|
||||
|
||||
_Signature:_ `NuiTree.Node(data, children)`
|
||||
|
||||
**Examples**
|
||||
|
||||
```lua
|
||||
local NuiTree = require("nui.tree")
|
||||
|
||||
local node = NuiTree.Node({ text = "b" }, {
|
||||
NuiTree.Node({ text = "b-1" }),
|
||||
NuiTree.Node({ text = "b-2" }),
|
||||
})
|
||||
```
|
||||
|
||||
### Parameters
|
||||
|
||||
#### `data`
|
||||
|
||||
**Type:** `table`
|
||||
|
||||
Data for the node. Can contain anything. The default `get_node_id`
|
||||
and `prepare_node` functions uses the `id` and `text` keys.
|
||||
|
||||
**Example**
|
||||
|
||||
```lua
|
||||
{
|
||||
id = "/usr/local/bin/lua",
|
||||
text = "lua"
|
||||
}
|
||||
```
|
||||
|
||||
If you don't want to provide those two values, you should consider
|
||||
providing your own `get_node_id` and `prepare_node` functions.
|
||||
|
||||
#### `children`
|
||||
|
||||
**Type:** `table`
|
||||
|
||||
List of `NuiTree.Node` objects.
|
||||
|
||||
### Methods
|
||||
|
||||
#### `node:get_id`
|
||||
|
||||
_Signature:_ `node:get_id()`
|
||||
|
||||
Returns node's id.
|
||||
|
||||
#### `node:get_depth`
|
||||
|
||||
_Signature:_ `node:get_depth()`
|
||||
|
||||
Returns node's depth.
|
||||
|
||||
#### `node:get_parent_id`
|
||||
|
||||
_Signature:_ `node:get_parent_id()`
|
||||
|
||||
Returns parent node's id.
|
||||
|
||||
#### `node:has_children`
|
||||
|
||||
_Signature:_ `node:has_children()`
|
||||
|
||||
Checks if node has children.
|
||||
|
||||
#### `node:get_child_ids`
|
||||
|
||||
_Signature:_ `node:get_child_ids() -> string[]`
|
||||
|
||||
Returns ids of child nodes.
|
||||
|
||||
#### `node:is_expanded`
|
||||
|
||||
_Signature:_ `node:is_expanded()`
|
||||
|
||||
Checks if node is expanded.
|
||||
|
||||
#### `node:expand`
|
||||
|
||||
_Signature:_ `node:expand()`
|
||||
|
||||
Expands node.
|
||||
|
||||
#### `node:collapse`
|
||||
|
||||
_Signature:_ `node:collapse()`
|
||||
|
||||
Collapses node.
|
||||
|
||||
## Wiki Page
|
||||
|
||||
You can find additional documentation/examples/guides/tips-n-tricks in [nui.tree wiki page](https://github.com/MunifTanjim/nui.nvim/wiki/nui.tree).
|
||||
482
.config/nvim/pack/tree/start/nui.nvim/lua/nui/tree/init.lua
Normal file
482
.config/nvim/pack/tree/start/nui.nvim/lua/nui/tree/init.lua
Normal file
|
|
@ -0,0 +1,482 @@
|
|||
local Object = require("nui.object")
|
||||
local _ = require("nui.utils")._
|
||||
local defaults = require("nui.utils").defaults
|
||||
local is_type = require("nui.utils").is_type
|
||||
local tree_util = require("nui.tree.util")
|
||||
|
||||
-- returns id of the first window that contains the buffer
|
||||
---@param bufnr number
|
||||
---@return number winid
|
||||
local function get_winid(bufnr)
|
||||
return vim.fn.win_findbuf(bufnr)[1]
|
||||
end
|
||||
|
||||
---@param nodes NuiTree.Node[]
|
||||
---@param parent_node? NuiTree.Node
|
||||
---@param get_node_id nui_tree_get_node_id
|
||||
---@return { by_id: table<string, NuiTree.Node>, root_ids: string[] }
|
||||
local function initialize_nodes(nodes, parent_node, get_node_id)
|
||||
local start_depth = parent_node and parent_node:get_depth() + 1 or 1
|
||||
|
||||
---@type table<string, NuiTree.Node>
|
||||
local by_id = {}
|
||||
---@type string[]
|
||||
local root_ids = {}
|
||||
|
||||
---@param node NuiTree.Node
|
||||
---@param depth number
|
||||
local function initialize(node, depth)
|
||||
node._depth = depth
|
||||
node._id = get_node_id(node)
|
||||
node._initialized = true
|
||||
|
||||
local node_id = node:get_id()
|
||||
|
||||
if by_id[node_id] then
|
||||
error("duplicate node id " .. node_id)
|
||||
end
|
||||
|
||||
by_id[node_id] = node
|
||||
|
||||
if depth == start_depth then
|
||||
table.insert(root_ids, node_id)
|
||||
end
|
||||
|
||||
if not node.__children or #node.__children == 0 then
|
||||
return
|
||||
end
|
||||
|
||||
if not node._child_ids then
|
||||
node._child_ids = {}
|
||||
end
|
||||
|
||||
for _, child_node in ipairs(node.__children) do
|
||||
child_node._parent_id = node_id
|
||||
initialize(child_node, depth + 1)
|
||||
table.insert(node._child_ids, child_node:get_id())
|
||||
end
|
||||
|
||||
node.__children = nil
|
||||
end
|
||||
|
||||
for _, node in ipairs(nodes) do
|
||||
node._parent_id = parent_node and parent_node:get_id() or nil
|
||||
initialize(node, start_depth)
|
||||
end
|
||||
|
||||
return {
|
||||
by_id = by_id,
|
||||
root_ids = root_ids,
|
||||
}
|
||||
end
|
||||
|
||||
---@class NuiTree.Node
|
||||
---@field _id string
|
||||
---@field _depth integer
|
||||
---@field _parent_id? string
|
||||
---@field _child_ids? string[]
|
||||
---@field __children? NuiTree.Node[]
|
||||
---@field [string] any
|
||||
local TreeNode = {
|
||||
super = nil,
|
||||
}
|
||||
|
||||
---@alias NuiTreeNode NuiTree.Node
|
||||
|
||||
---@return string
|
||||
function TreeNode:get_id()
|
||||
return self._id
|
||||
end
|
||||
|
||||
---@return number
|
||||
function TreeNode:get_depth()
|
||||
return self._depth
|
||||
end
|
||||
|
||||
---@return string|nil
|
||||
function TreeNode:get_parent_id()
|
||||
return self._parent_id
|
||||
end
|
||||
|
||||
---@return boolean
|
||||
function TreeNode:has_children()
|
||||
local items = self._child_ids or self.__children
|
||||
return items and #items > 0 or false
|
||||
end
|
||||
|
||||
---@return string[]
|
||||
function TreeNode:get_child_ids()
|
||||
return self._child_ids or {}
|
||||
end
|
||||
|
||||
---@return boolean
|
||||
function TreeNode:is_expanded()
|
||||
return self._is_expanded
|
||||
end
|
||||
|
||||
---@return boolean is_updated
|
||||
function TreeNode:expand()
|
||||
if (self._child_ids or self.__children) and not self:is_expanded() then
|
||||
self._is_expanded = true
|
||||
return true
|
||||
end
|
||||
return false
|
||||
end
|
||||
|
||||
---@return boolean is_updated
|
||||
function TreeNode:collapse()
|
||||
if self:is_expanded() then
|
||||
self._is_expanded = false
|
||||
return true
|
||||
end
|
||||
return false
|
||||
end
|
||||
|
||||
--luacheck: push no max line length
|
||||
|
||||
---@alias nui_tree_get_node_id fun(node: NuiTree.Node): string
|
||||
---@alias nui_tree_prepare_node fun(node: NuiTree.Node, parent_node?: NuiTree.Node): nil | string | string[] | NuiLine | NuiLine[]
|
||||
|
||||
--luacheck: pop
|
||||
|
||||
---@class nui_tree_internal
|
||||
---@field buf_options table<string, any>
|
||||
---@field get_node_id nui_tree_get_node_id
|
||||
---@field linenr { [1]?: integer, [2]?: integer }
|
||||
---@field linenr_by_node_id table<string, { [1]: integer, [2]: integer }>
|
||||
---@field node_id_by_linenr table<integer, string>
|
||||
---@field prepare_node nui_tree_prepare_node
|
||||
---@field win_options table<string, any> # deprecated
|
||||
|
||||
---@class nui_tree_options
|
||||
---@field bufnr integer
|
||||
---@field ns_id? string|integer
|
||||
---@field nodes? NuiTree.Node[]
|
||||
---@field get_node_id? fun(node: NuiTree.Node): string
|
||||
---@field prepare_node? fun(node: NuiTree.Node, parent_node?: NuiTree.Node): nil|string|string[]|NuiLine|NuiLine[]
|
||||
|
||||
---@class NuiTree
|
||||
---@field bufnr integer
|
||||
---@field nodes { by_id: table<string,NuiTree.Node>, root_ids: string[] }
|
||||
---@field ns_id integer
|
||||
---@field private _ nui_tree_internal
|
||||
---@field winid number # @deprecated
|
||||
local Tree = Object("NuiTree")
|
||||
|
||||
---@param options nui_tree_options
|
||||
function Tree:init(options)
|
||||
---@deprecated
|
||||
if options.winid then
|
||||
if not vim.api.nvim_win_is_valid(options.winid) then
|
||||
error("invalid winid " .. options.winid)
|
||||
end
|
||||
|
||||
self.winid = options.winid
|
||||
self.bufnr = vim.api.nvim_win_get_buf(self.winid)
|
||||
end
|
||||
|
||||
if options.bufnr then
|
||||
if not vim.api.nvim_buf_is_valid(options.bufnr) then
|
||||
error("invalid bufnr " .. options.bufnr)
|
||||
end
|
||||
|
||||
self.bufnr = options.bufnr
|
||||
self.winid = nil
|
||||
end
|
||||
|
||||
if not self.bufnr then
|
||||
error("missing bufnr")
|
||||
end
|
||||
|
||||
self.ns_id = _.normalize_namespace_id(options.ns_id)
|
||||
|
||||
self._ = {
|
||||
buf_options = vim.tbl_extend("force", {
|
||||
bufhidden = "hide",
|
||||
buflisted = false,
|
||||
buftype = "nofile",
|
||||
modifiable = false,
|
||||
readonly = true,
|
||||
swapfile = false,
|
||||
undolevels = 0,
|
||||
}, defaults(options.buf_options, {})),
|
||||
---@deprecated
|
||||
win_options = vim.tbl_extend("force", {
|
||||
foldcolumn = "0",
|
||||
foldmethod = "manual",
|
||||
wrap = false,
|
||||
}, defaults(options.win_options, {})),
|
||||
get_node_id = defaults(options.get_node_id, tree_util.default_get_node_id),
|
||||
prepare_node = defaults(options.prepare_node, tree_util.default_prepare_node),
|
||||
|
||||
linenr = {},
|
||||
}
|
||||
|
||||
_.set_buf_options(self.bufnr, self._.buf_options)
|
||||
|
||||
---@deprecated
|
||||
if self.winid then
|
||||
_.set_win_options(self.winid, self._.win_options)
|
||||
end
|
||||
|
||||
self:set_nodes(defaults(options.nodes, {}))
|
||||
end
|
||||
|
||||
---@generic D : table
|
||||
---@param data D data table
|
||||
---@param children? NuiTree.Node[]
|
||||
---@return NuiTree.Node|D
|
||||
function Tree.Node(data, children)
|
||||
---@type NuiTree.Node
|
||||
local self = {
|
||||
__children = children,
|
||||
_initialized = false,
|
||||
_is_expanded = false,
|
||||
_child_ids = nil,
|
||||
_parent_id = nil,
|
||||
---@diagnostic disable-next-line: assign-type-mismatch
|
||||
_depth = nil,
|
||||
---@diagnostic disable-next-line: assign-type-mismatch
|
||||
_id = nil,
|
||||
}
|
||||
|
||||
self = setmetatable(vim.tbl_extend("keep", self, data), {
|
||||
__index = TreeNode,
|
||||
__name = "NuiTree.Node",
|
||||
})
|
||||
|
||||
return self
|
||||
end
|
||||
|
||||
---@param node_id_or_linenr? string | integer
|
||||
---@return NuiTree.Node|nil node
|
||||
---@return nil|integer linenr
|
||||
---@return nil|integer linenr
|
||||
function Tree:get_node(node_id_or_linenr)
|
||||
if is_type("string", node_id_or_linenr) then
|
||||
return self.nodes.by_id[node_id_or_linenr], unpack(self._.linenr_by_node_id[node_id_or_linenr] or {})
|
||||
end
|
||||
|
||||
local winid = get_winid(self.bufnr)
|
||||
local linenr = node_id_or_linenr or vim.api.nvim_win_get_cursor(winid)[1]
|
||||
local node_id = self._.node_id_by_linenr[linenr]
|
||||
return self.nodes.by_id[node_id], unpack(self._.linenr_by_node_id[node_id] or {})
|
||||
end
|
||||
|
||||
---@param parent_id? string parent node's id
|
||||
---@return NuiTree.Node[] nodes
|
||||
function Tree:get_nodes(parent_id)
|
||||
local node_ids = {}
|
||||
|
||||
if parent_id then
|
||||
local parent_node = self.nodes.by_id[parent_id]
|
||||
if parent_node then
|
||||
node_ids = parent_node._child_ids
|
||||
end
|
||||
else
|
||||
node_ids = self.nodes.root_ids
|
||||
end
|
||||
|
||||
return vim.tbl_map(function(id)
|
||||
return self.nodes.by_id[id]
|
||||
end, node_ids or {})
|
||||
end
|
||||
|
||||
---@param nodes NuiTree.Node[]
|
||||
---@param parent_node? NuiTree.Node
|
||||
function Tree:_add_nodes(nodes, parent_node)
|
||||
local new_nodes = initialize_nodes(nodes, parent_node, self._.get_node_id)
|
||||
|
||||
self.nodes.by_id = vim.tbl_extend("force", self.nodes.by_id, new_nodes.by_id)
|
||||
|
||||
if parent_node then
|
||||
if not parent_node._child_ids then
|
||||
parent_node._child_ids = {}
|
||||
end
|
||||
|
||||
for _, id in ipairs(new_nodes.root_ids) do
|
||||
table.insert(parent_node._child_ids, id)
|
||||
end
|
||||
else
|
||||
for _, id in ipairs(new_nodes.root_ids) do
|
||||
table.insert(self.nodes.root_ids, id)
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
---@param nodes NuiTree.Node[]
|
||||
---@param parent_id? string parent node's id
|
||||
function Tree:set_nodes(nodes, parent_id)
|
||||
self._.node_id_by_linenr = {}
|
||||
self._.linenr_by_node_id = {}
|
||||
|
||||
if not parent_id then
|
||||
self.nodes = { by_id = {}, root_ids = {} }
|
||||
self:_add_nodes(nodes)
|
||||
return
|
||||
end
|
||||
|
||||
local parent_node = self.nodes.by_id[parent_id]
|
||||
if not parent_node then
|
||||
error("invalid parent_id " .. parent_id)
|
||||
end
|
||||
|
||||
if parent_node._child_ids then
|
||||
for _, node_id in ipairs(parent_node._child_ids) do
|
||||
self.nodes.by_id[node_id] = nil
|
||||
end
|
||||
|
||||
parent_node._child_ids = nil
|
||||
end
|
||||
|
||||
self:_add_nodes(nodes, parent_node)
|
||||
end
|
||||
|
||||
---@param node NuiTree.Node
|
||||
---@param parent_id? string parent node's id
|
||||
function Tree:add_node(node, parent_id)
|
||||
local parent_node = self.nodes.by_id[parent_id]
|
||||
if parent_id and not parent_node then
|
||||
error("invalid parent_id " .. parent_id)
|
||||
end
|
||||
|
||||
self:_add_nodes({ node }, parent_node)
|
||||
end
|
||||
|
||||
local function remove_node(tree, node_id)
|
||||
local node = tree.nodes.by_id[node_id]
|
||||
if node:has_children() then
|
||||
for _, child_id in ipairs(node._child_ids) do
|
||||
-- We might want to store the nodes and return them with the node itself?
|
||||
-- We should _really_ not be doing this recursively, but it will work for now
|
||||
remove_node(tree, child_id)
|
||||
end
|
||||
end
|
||||
tree.nodes.by_id[node_id] = nil
|
||||
return node
|
||||
end
|
||||
|
||||
---@param node_id string
|
||||
---@return NuiTree.Node
|
||||
function Tree:remove_node(node_id)
|
||||
local node = remove_node(self, node_id)
|
||||
local parent_id = node._parent_id
|
||||
if parent_id then
|
||||
local parent_node = self.nodes.by_id[parent_id]
|
||||
parent_node._child_ids = vim.tbl_filter(function(id)
|
||||
return id ~= node_id
|
||||
end, parent_node._child_ids)
|
||||
else
|
||||
self.nodes.root_ids = vim.tbl_filter(function(id)
|
||||
return id ~= node_id
|
||||
end, self.nodes.root_ids)
|
||||
end
|
||||
return node
|
||||
end
|
||||
|
||||
---@param linenr_start number start line number (1-indexed)
|
||||
---@return (string|NuiLine)[]|{ len: integer } lines
|
||||
function Tree:_prepare_content(linenr_start)
|
||||
local internal = self._
|
||||
|
||||
local by_id = self.nodes.by_id
|
||||
|
||||
---@type { [1]: string|NuiLine }
|
||||
local list_wrapper = {}
|
||||
|
||||
local tree_linenr = 0
|
||||
local lines = { len = tree_linenr }
|
||||
|
||||
local node_id_by_linenr = {}
|
||||
internal.node_id_by_linenr = node_id_by_linenr
|
||||
|
||||
local linenr_by_node_id = {}
|
||||
internal.linenr_by_node_id = linenr_by_node_id
|
||||
|
||||
local function prepare(node_id, parent_node)
|
||||
local node = by_id[node_id]
|
||||
if not node then
|
||||
return
|
||||
end
|
||||
|
||||
local node_lines = internal.prepare_node(node, parent_node)
|
||||
if node_lines then
|
||||
if type(node_lines) ~= "table" or node_lines.content then
|
||||
list_wrapper[1] = node_lines
|
||||
node_lines = list_wrapper
|
||||
end
|
||||
---@cast node_lines -string, -NuiLine
|
||||
|
||||
local node_linenr = linenr_by_node_id[node_id] or {}
|
||||
for node_line_idx = 1, #node_lines do
|
||||
local node_line = node_lines[node_line_idx]
|
||||
|
||||
tree_linenr = tree_linenr + 1
|
||||
local buffer_linenr = tree_linenr + linenr_start - 1
|
||||
|
||||
lines[tree_linenr] = node_line
|
||||
|
||||
node_id_by_linenr[buffer_linenr] = node_id
|
||||
|
||||
if node_line_idx == 1 then
|
||||
node_linenr[1] = buffer_linenr
|
||||
end
|
||||
node_linenr[2] = buffer_linenr
|
||||
end
|
||||
linenr_by_node_id[node_id] = node_linenr
|
||||
end
|
||||
|
||||
local child_ids = node._child_ids
|
||||
if child_ids and node._is_expanded then
|
||||
for child_id_idx = 1, #child_ids do
|
||||
prepare(child_ids[child_id_idx], node)
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
local root_ids = self.nodes.root_ids
|
||||
for node_id_idx = 1, #root_ids do
|
||||
prepare(root_ids[node_id_idx])
|
||||
end
|
||||
|
||||
lines.len = tree_linenr
|
||||
|
||||
return lines
|
||||
end
|
||||
|
||||
---@param linenr_start? number start line number (1-indexed)
|
||||
function Tree:render(linenr_start)
|
||||
linenr_start = math.max(1, linenr_start or self._.linenr[1] or 1)
|
||||
|
||||
local prev_linenr = { self._.linenr[1], self._.linenr[2] }
|
||||
|
||||
local lines = self:_prepare_content(linenr_start)
|
||||
local line_idx = lines.len
|
||||
lines.len = nil
|
||||
|
||||
_.set_buf_options(self.bufnr, { modifiable = true, readonly = false })
|
||||
|
||||
_.clear_namespace(self.bufnr, self.ns_id, prev_linenr[1], prev_linenr[2])
|
||||
|
||||
-- if linenr_start was shifted downwards,
|
||||
-- clear the previously rendered lines above.
|
||||
_.clear_lines(
|
||||
self.bufnr,
|
||||
math.min(linenr_start, prev_linenr[1] or linenr_start),
|
||||
prev_linenr[1] and linenr_start - 1 or 0
|
||||
)
|
||||
|
||||
-- for initial render, start inserting in a single line.
|
||||
-- for subsequent renders, replace the lines from previous render.
|
||||
_.render_lines(lines, self.bufnr, self.ns_id, linenr_start, prev_linenr[1] and prev_linenr[2] or linenr_start)
|
||||
|
||||
_.set_buf_options(self.bufnr, { modifiable = false, readonly = true })
|
||||
|
||||
self._.linenr[1], self._.linenr[2] = linenr_start, line_idx + linenr_start - 1
|
||||
end
|
||||
|
||||
---@alias NuiTree.constructor fun(options: nui_tree_options): NuiTree
|
||||
---@type NuiTree|NuiTree.constructor
|
||||
local NuiTree = Tree
|
||||
|
||||
return NuiTree
|
||||
70
.config/nvim/pack/tree/start/nui.nvim/lua/nui/tree/util.lua
Normal file
70
.config/nvim/pack/tree/start/nui.nvim/lua/nui/tree/util.lua
Normal file
|
|
@ -0,0 +1,70 @@
|
|||
local NuiLine = require("nui.line")
|
||||
|
||||
local mod = {}
|
||||
|
||||
---@param node NuiTree.Node
|
||||
---@return string node_id
|
||||
function mod.default_get_node_id(node)
|
||||
if node.id then
|
||||
return "-" .. node.id
|
||||
end
|
||||
|
||||
if node.text then
|
||||
local texts = node.text
|
||||
if type(node.text) ~= "table" or node.text.content then
|
||||
texts = { node.text }
|
||||
end
|
||||
return string.format(
|
||||
"%s-%s-%s",
|
||||
node._parent_id or "",
|
||||
node._depth,
|
||||
table.concat(
|
||||
vim.tbl_map(function(text)
|
||||
if type(text) == "string" then
|
||||
return text
|
||||
end
|
||||
return text:content()
|
||||
end, texts),
|
||||
"-"
|
||||
)
|
||||
)
|
||||
end
|
||||
|
||||
return "-" .. math.random()
|
||||
end
|
||||
|
||||
---@param node NuiTree.Node
|
||||
---@return NuiLine[]
|
||||
function mod.default_prepare_node(node)
|
||||
if not node.text then
|
||||
error("missing node.text")
|
||||
end
|
||||
|
||||
local texts = node.text
|
||||
|
||||
if type(node.text) ~= "table" or node.text.content then
|
||||
texts = { node.text }
|
||||
end
|
||||
|
||||
local lines = {}
|
||||
|
||||
for i, text in ipairs(texts) do
|
||||
local line = NuiLine()
|
||||
|
||||
line:append(string.rep(" ", node._depth - 1))
|
||||
|
||||
if i == 1 and node:has_children() then
|
||||
line:append(node:is_expanded() and " " or " ")
|
||||
else
|
||||
line:append(" ")
|
||||
end
|
||||
|
||||
line:append(text)
|
||||
|
||||
table.insert(lines, line)
|
||||
end
|
||||
|
||||
return lines
|
||||
end
|
||||
|
||||
return mod
|
||||
478
.config/nvim/pack/tree/start/nui.nvim/lua/nui/utils/autocmd.lua
Normal file
478
.config/nvim/pack/tree/start/nui.nvim/lua/nui/utils/autocmd.lua
Normal file
|
|
@ -0,0 +1,478 @@
|
|||
local buf_storage = require("nui.utils.buf_storage")
|
||||
local is_type = require("nui.utils").is_type
|
||||
local feature = require("nui.utils")._.feature
|
||||
|
||||
local autocmd = {
|
||||
event = {
|
||||
-- after adding a buffer to the buffer list
|
||||
BufAdd = "BufAdd",
|
||||
-- deleting a buffer from the buffer list
|
||||
BufDelete = "BufDelete",
|
||||
-- after entering a buffer
|
||||
BufEnter = "BufEnter",
|
||||
-- after renaming a buffer
|
||||
BufFilePost = "BufFilePost",
|
||||
-- before renaming a buffer
|
||||
BufFilePre = "BufFilePre",
|
||||
-- just after buffer becomes hidden
|
||||
BufHidden = "BufHidden",
|
||||
-- before leaving a buffer
|
||||
BufLeave = "BufLeave",
|
||||
-- after the 'modified' state of a buffer changes
|
||||
BufModifiedSet = "BufModifiedSet",
|
||||
-- after creating any buffer
|
||||
BufNew = "BufNew",
|
||||
-- when creating a buffer for a new file
|
||||
BufNewFile = "BufNewFile",
|
||||
-- read buffer using command
|
||||
BufReadCmd = "BufReadCmd",
|
||||
-- after reading a buffer
|
||||
BufReadPost = "BufReadPost",
|
||||
-- before reading a buffer
|
||||
BufReadPre = "BufReadPre",
|
||||
-- just before unloading a buffer
|
||||
BufUnload = "BufUnload",
|
||||
-- after showing a buffer in a window
|
||||
BufWinEnter = "BufWinEnter",
|
||||
-- just after buffer removed from window
|
||||
BufWinLeave = "BufWinLeave",
|
||||
-- just before really deleting a buffer
|
||||
BufWipeout = "BufWipeout",
|
||||
-- write buffer using command
|
||||
BufWriteCmd = "BufWriteCmd",
|
||||
-- after writing a buffer
|
||||
BufWritePost = "BufWritePost",
|
||||
-- before writing a buffer
|
||||
BufWritePre = "BufWritePre",
|
||||
-- info was received about channel
|
||||
ChanInfo = "ChanInfo",
|
||||
-- channel was opened
|
||||
ChanOpen = "ChanOpen",
|
||||
-- command undefined
|
||||
CmdUndefined = "CmdUndefined",
|
||||
-- command line was modified
|
||||
CmdlineChanged = "CmdlineChanged",
|
||||
-- after entering cmdline mode
|
||||
CmdlineEnter = "CmdlineEnter",
|
||||
-- before leaving cmdline mode
|
||||
CmdlineLeave = "CmdlineLeave",
|
||||
-- after entering the cmdline window
|
||||
CmdWinEnter = "CmdwinEnter",
|
||||
-- before leaving the cmdline window
|
||||
CmdWinLeave = "CmdwinLeave",
|
||||
-- after loading a colorscheme
|
||||
ColorScheme = "ColorScheme",
|
||||
-- before loading a colorscheme
|
||||
ColorSchemePre = "ColorSchemePre",
|
||||
-- after popup menu changed
|
||||
CompleteChanged = "CompleteChanged",
|
||||
-- after finishing insert complete
|
||||
CompleteDone = "CompleteDone",
|
||||
-- idem, before clearing info
|
||||
CompleteDonePre = "CompleteDonePre",
|
||||
-- cursor in same position for a while
|
||||
CursorHold = "CursorHold",
|
||||
-- idem, in Insert mode
|
||||
CursorHoldI = "CursorHoldI",
|
||||
-- cursor was moved
|
||||
CursorMoved = "CursorMoved",
|
||||
-- cursor was moved in Insert mode
|
||||
CursorMovedI = "CursorMovedI",
|
||||
-- diffs have been updated
|
||||
DiffUpdated = "DiffUpdated",
|
||||
-- directory changed
|
||||
DirChanged = "DirChanged",
|
||||
-- after changing the 'encoding' option
|
||||
EncodingChanged = "EncodingChanged",
|
||||
-- before exiting
|
||||
ExitPre = "ExitPre",
|
||||
-- append to a file using command
|
||||
FileAppendCmd = "FileAppendCmd",
|
||||
-- after appending to a file
|
||||
FileAppendPost = "FileAppendPost",
|
||||
-- before appending to a file
|
||||
FileAppendPre = "FileAppendPre",
|
||||
-- before first change to read-only file
|
||||
FileChangedRO = "FileChangedRO",
|
||||
-- after shell command that changed file
|
||||
FileChangedShell = "FileChangedShell",
|
||||
-- after (not) reloading changed file
|
||||
FileChangedShellPost = "FileChangedShellPost",
|
||||
-- read from a file using command
|
||||
FileReadCmd = "FileReadCmd",
|
||||
-- after reading a file
|
||||
FileReadPost = "FileReadPost",
|
||||
-- before reading a file
|
||||
FileReadPre = "FileReadPre",
|
||||
-- new file type detected (user defined)
|
||||
FileType = "FileType",
|
||||
-- write to a file using command
|
||||
FileWriteCmd = "FileWriteCmd",
|
||||
-- after writing a file
|
||||
FileWritePost = "FileWritePost",
|
||||
-- before writing a file
|
||||
FileWritePre = "FileWritePre",
|
||||
-- after reading from a filter
|
||||
FilterReadPost = "FilterReadPost",
|
||||
-- before reading from a filter
|
||||
FilterReadPre = "FilterReadPre",
|
||||
-- after writing to a filter
|
||||
FilterWritePost = "FilterWritePost",
|
||||
-- before writing to a filter
|
||||
FilterWritePre = "FilterWritePre",
|
||||
-- got the focus
|
||||
FocusGained = "FocusGained",
|
||||
-- lost the focus to another app
|
||||
FocusLost = "FocusLost",
|
||||
-- if calling a function which doesn't exist
|
||||
FuncUndefined = "FuncUndefined",
|
||||
-- after starting the GUI
|
||||
GUIEnter = "GUIEnter",
|
||||
-- after starting the GUI failed
|
||||
GUIFailed = "GUIFailed",
|
||||
-- when changing Insert/Replace mode
|
||||
InsertChange = "InsertChange",
|
||||
-- before inserting a char
|
||||
InsertCharPre = "InsertCharPre",
|
||||
-- when entering Insert mode
|
||||
InsertEnter = "InsertEnter",
|
||||
-- just after leaving Insert mode
|
||||
InsertLeave = "InsertLeave",
|
||||
-- just before leaving Insert mode
|
||||
InsertLeavePre = "InsertLeavePre",
|
||||
-- just before popup menu is displayed
|
||||
MenuPopup = "MenuPopup",
|
||||
-- after changing the mode
|
||||
ModeChanged = "ModeChanged",
|
||||
-- after setting any option
|
||||
OptionSet = "OptionSet",
|
||||
-- after :make, :grep etc.
|
||||
QuickFixCmdPost = "QuickFixCmdPost",
|
||||
-- before :make, :grep etc.
|
||||
QuickFixCmdPre = "QuickFixCmdPre",
|
||||
-- before :quit
|
||||
QuitPre = "QuitPre",
|
||||
-- upon string reception from a remote vim
|
||||
RemoteReply = "RemoteReply",
|
||||
-- when the search wraps around the document
|
||||
SearchWrapped = "SearchWrapped",
|
||||
-- after loading a session file
|
||||
SessionLoadPost = "SessionLoadPost",
|
||||
-- after ":!cmd"
|
||||
ShellCmdPost = "ShellCmdPost",
|
||||
-- after ":1,2!cmd", ":w !cmd", ":r !cmd".
|
||||
ShellFilterPost = "ShellFilterPost",
|
||||
-- after nvim process received a signal
|
||||
Signal = "Signal",
|
||||
-- sourcing a Vim script using command
|
||||
SourceCmd = "SourceCmd",
|
||||
-- after sourcing a Vim script
|
||||
SourcePost = "SourcePost",
|
||||
-- before sourcing a Vim script
|
||||
SourcePre = "SourcePre",
|
||||
-- spell file missing
|
||||
SpellFileMissing = "SpellFileMissing",
|
||||
-- after reading from stdin
|
||||
StdinReadPost = "StdinReadPost",
|
||||
-- before reading from stdin
|
||||
StdinReadPre = "StdinReadPre",
|
||||
-- found existing swap file
|
||||
SwapExists = "SwapExists",
|
||||
-- syntax selected
|
||||
Syntax = "Syntax",
|
||||
-- a tab has closed
|
||||
TabClosed = "TabClosed",
|
||||
-- after entering a tab page
|
||||
TabEnter = "TabEnter",
|
||||
-- before leaving a tab page
|
||||
TabLeave = "TabLeave",
|
||||
-- when creating a new tab
|
||||
TabNew = "TabNew",
|
||||
-- after entering a new tab
|
||||
TabNewEntered = "TabNewEntered",
|
||||
-- after changing 'term'
|
||||
TermChanged = "TermChanged",
|
||||
-- after the process exits
|
||||
TermClose = "TermClose",
|
||||
-- after entering Terminal mode
|
||||
TermEnter = "TermEnter",
|
||||
-- after leaving Terminal mode
|
||||
TermLeave = "TermLeave",
|
||||
-- after opening a terminal buffer
|
||||
TermOpen = "TermOpen",
|
||||
-- after setting "v:termresponse"
|
||||
TermResponse = "TermResponse",
|
||||
-- text was modified
|
||||
TextChanged = "TextChanged",
|
||||
-- text was modified in Insert mode(no popup)
|
||||
TextChangedI = "TextChangedI",
|
||||
-- text was modified in Insert mode(popup)
|
||||
TextChangedP = "TextChangedP",
|
||||
-- after a yank or delete was done (y, d, c)
|
||||
TextYankPost = "TextYankPost",
|
||||
-- after UI attaches
|
||||
UIEnter = "UIEnter",
|
||||
-- after UI detaches
|
||||
UILeave = "UILeave",
|
||||
-- user defined autocommand
|
||||
User = "User",
|
||||
-- whenthe user presses the same key 42 times
|
||||
UserGettingBored = "UserGettingBored",
|
||||
-- after starting Vim
|
||||
VimEnter = "VimEnter",
|
||||
-- before exiting Vim
|
||||
VimLeave = "VimLeave",
|
||||
-- before exiting Vim and writing ShaDa file
|
||||
VimLeavePre = "VimLeavePre",
|
||||
-- after Vim window was resized
|
||||
VimResized = "VimResized",
|
||||
-- after Nvim is resumed
|
||||
VimResume = "VimResume",
|
||||
-- before Nvim is suspended
|
||||
VimSuspend = "VimSuspend",
|
||||
-- after closing a window
|
||||
WinClosed = "WinClosed",
|
||||
-- after entering a window
|
||||
WinEnter = "WinEnter",
|
||||
-- before leaving a window
|
||||
WinLeave = "WinLeave",
|
||||
-- when entering a new window
|
||||
WinNew = "WinNew",
|
||||
-- after scrolling a window
|
||||
WinScrolled = "WinScrolled",
|
||||
|
||||
-- alias for `BufAdd`
|
||||
BufCreate = "BufAdd",
|
||||
-- alias for `BufReadPost`
|
||||
BufRead = "BufReadPost",
|
||||
-- alias for `BufWritePre`
|
||||
BufWrite = "BufWritePre",
|
||||
-- alias for `EncodingChanged`
|
||||
FileEncoding = "EncodingChanged",
|
||||
},
|
||||
buf = {
|
||||
storage = buf_storage.create("nui.utils.autocmd", { _next_handler_id = 1 }),
|
||||
},
|
||||
}
|
||||
|
||||
---@param callback fun(event: table): nil
|
||||
---@param bufnr integer
|
||||
local function to_stored_handler(callback, bufnr)
|
||||
local handler_id = autocmd.buf.storage[bufnr]._next_handler_id
|
||||
autocmd.buf.storage[bufnr]._next_handler_id = handler_id + 1
|
||||
|
||||
autocmd.buf.storage[bufnr][handler_id] = callback
|
||||
|
||||
local command = string.format(":lua require('nui.utils.autocmd').execute_stored_handler(%s, %s)", bufnr, handler_id)
|
||||
|
||||
return command
|
||||
end
|
||||
|
||||
---@param bufnr integer
|
||||
---@param handler_id number
|
||||
function autocmd.execute_stored_handler(bufnr, handler_id)
|
||||
local handler = autocmd.buf.storage[bufnr][handler_id]
|
||||
if is_type("function", handler) then
|
||||
handler()
|
||||
end
|
||||
end
|
||||
|
||||
---@param name string
|
||||
---@param opts { clear?: boolean }
|
||||
function autocmd.create_group(name, opts)
|
||||
if feature.lua_autocmd then
|
||||
return vim.api.nvim_create_augroup(name, opts)
|
||||
end
|
||||
|
||||
vim.cmd(string.format(
|
||||
[[
|
||||
augroup %s
|
||||
%s
|
||||
augroup end
|
||||
]],
|
||||
name,
|
||||
opts.clear and "autocmd!" or ""
|
||||
))
|
||||
end
|
||||
|
||||
---@param name string
|
||||
function autocmd.delete_group(name)
|
||||
if feature.lua_autocmd then
|
||||
return vim.api.nvim_del_augroup_by_name(name)
|
||||
end
|
||||
|
||||
vim.cmd(string.format(
|
||||
[[
|
||||
autocmd! %s
|
||||
augroup! %s
|
||||
]],
|
||||
name,
|
||||
name
|
||||
))
|
||||
end
|
||||
|
||||
---@param event string|string[]
|
||||
---@param opts table
|
||||
---@param bufnr? integer # to store callback if lua autocmd is not available
|
||||
function autocmd.create(event, opts, bufnr)
|
||||
if feature.lua_autocmd then
|
||||
return vim.api.nvim_create_autocmd(event, opts)
|
||||
end
|
||||
|
||||
event = type(event) == "table" and table.concat(event, ",") or event --[[@as string]]
|
||||
local pattern = is_type("table", opts.pattern) and table.concat(opts.pattern, ",") or opts.pattern
|
||||
if opts.buffer then
|
||||
pattern = string.format("<buffer=%s>", opts.buffer)
|
||||
end
|
||||
|
||||
if opts.callback then
|
||||
local buffer = opts.buffer or bufnr
|
||||
if not buffer then
|
||||
error("[nui.utils.autocmd] missing param: bufnr")
|
||||
end
|
||||
opts.command = to_stored_handler(opts.callback, buffer)
|
||||
end
|
||||
|
||||
vim.cmd(
|
||||
string.format(
|
||||
"autocmd %s %s %s %s %s %s",
|
||||
opts.group or "",
|
||||
event,
|
||||
pattern,
|
||||
opts.once and "++once" or "",
|
||||
opts.nested and "++nested" or "",
|
||||
opts.command
|
||||
)
|
||||
)
|
||||
end
|
||||
|
||||
---@param opts table
|
||||
function autocmd.delete(opts)
|
||||
if feature.lua_autocmd then
|
||||
for _, item in ipairs(vim.api.nvim_get_autocmds(opts)) do
|
||||
if item.id then
|
||||
vim.api.nvim_del_autocmd(item.id)
|
||||
end
|
||||
end
|
||||
|
||||
return
|
||||
end
|
||||
|
||||
local event = is_type("table", opts.event) and table.concat(opts.event, ",") or opts.event
|
||||
local pattern = is_type("table", opts.pattern) and table.concat(opts.pattern, ",") or opts.pattern
|
||||
if opts.buffer then
|
||||
pattern = string.format("<buffer=%s>", opts.buffer)
|
||||
end
|
||||
|
||||
vim.cmd(string.format("autocmd! %s %s %s", opts.group or "", event or "*", pattern or ""))
|
||||
end
|
||||
|
||||
---@param event string|string[]
|
||||
---@param opts table
|
||||
function autocmd.exec(event, opts)
|
||||
local events = type(event) == "table" and event or { event } --[=[@as string[]]=]
|
||||
|
||||
if feature.lua_autocmd then
|
||||
vim.api.nvim_exec_autocmds(events, {
|
||||
group = opts.group,
|
||||
pattern = opts.pattern,
|
||||
buffer = opts.buffer,
|
||||
modeline = opts.modeline,
|
||||
data = opts.data,
|
||||
})
|
||||
|
||||
return
|
||||
end
|
||||
|
||||
for _, event_name in ipairs(events) do
|
||||
local command = string.format(
|
||||
[[doautocmd %s %s %s %s]],
|
||||
opts.modeline == false and "<nomodeline>" or "",
|
||||
opts.group or "",
|
||||
event_name,
|
||||
opts.pattern or ""
|
||||
)
|
||||
|
||||
if opts.buffer then
|
||||
vim.api.nvim_buf_call(opts.buffer, function()
|
||||
vim.cmd(command)
|
||||
end)
|
||||
else
|
||||
vim.cmd(command)
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
-- @deprecated
|
||||
---@deprecated
|
||||
---@param event string | string[]
|
||||
---@param pattern string | string[]
|
||||
---@param cmd string
|
||||
---@param options nil | table<"'once'" | "'nested'", boolean>
|
||||
function autocmd.define(event, pattern, cmd, options)
|
||||
local opts = options or {}
|
||||
opts.pattern = pattern
|
||||
opts.command = cmd
|
||||
autocmd.create(event, opts)
|
||||
end
|
||||
|
||||
-- @deprecated
|
||||
---@deprecated
|
||||
---@param group_name string
|
||||
---@param auto_clear boolean
|
||||
---@param definitions table<"'event'" | "'pattern'" | "'cmd'" | "'options'", any>
|
||||
function autocmd.define_grouped(group_name, auto_clear, definitions)
|
||||
if not is_type("boolean", auto_clear) then
|
||||
error("invalid param type: auto_clear, expected boolean")
|
||||
end
|
||||
|
||||
autocmd.create_group(group_name, { clear = auto_clear })
|
||||
|
||||
for _, definition in ipairs(definitions) do
|
||||
autocmd.define(definition.event, definition.pattern, definition.cmd, definition.options)
|
||||
end
|
||||
end
|
||||
|
||||
-- @deprecated
|
||||
---@deprecated
|
||||
---@param group_name nil | string
|
||||
---@param event nil | string | string[]
|
||||
---@param pattern nil | string | string[]
|
||||
function autocmd.remove(group_name, event, pattern)
|
||||
autocmd.delete({
|
||||
event = event,
|
||||
group = group_name,
|
||||
pattern = pattern,
|
||||
})
|
||||
end
|
||||
|
||||
---@param bufnr number
|
||||
---@param event string | string[]
|
||||
---@param handler string | function
|
||||
---@param options nil | table<"'once'" | "'nested'", boolean>
|
||||
function autocmd.buf.define(bufnr, event, handler, options)
|
||||
local opts = options or {}
|
||||
|
||||
opts.buffer = bufnr
|
||||
|
||||
if is_type("function", handler) then
|
||||
opts.callback = handler
|
||||
else
|
||||
opts.command = handler
|
||||
end
|
||||
|
||||
autocmd.create(event, opts, bufnr)
|
||||
end
|
||||
|
||||
---@param bufnr number
|
||||
---@param group_name nil | string
|
||||
---@param event nil | string | string[]
|
||||
function autocmd.buf.remove(bufnr, group_name, event)
|
||||
autocmd.delete({
|
||||
buffer = bufnr,
|
||||
event = event,
|
||||
group = group_name,
|
||||
})
|
||||
end
|
||||
|
||||
return autocmd
|
||||
|
|
@ -0,0 +1,33 @@
|
|||
local defaults = require("nui.utils").defaults
|
||||
|
||||
local buf_storage = {
|
||||
_registry = {},
|
||||
}
|
||||
|
||||
---@param storage_name string
|
||||
---@param default_value any
|
||||
---@return table<number, any>
|
||||
function buf_storage.create(storage_name, default_value)
|
||||
local storage = setmetatable({}, {
|
||||
__index = function(tbl, bufnr)
|
||||
rawset(tbl, bufnr, vim.deepcopy(defaults(default_value, {})))
|
||||
|
||||
-- TODO: can `buf_storage.cleanup` be automatically (and reliably) triggered on `BufWipeout`?
|
||||
|
||||
return tbl[bufnr]
|
||||
end,
|
||||
})
|
||||
|
||||
buf_storage._registry[storage_name] = storage
|
||||
|
||||
return storage
|
||||
end
|
||||
|
||||
---@param bufnr number
|
||||
function buf_storage.cleanup(bufnr)
|
||||
for _, storage in pairs(buf_storage._registry) do
|
||||
rawset(storage, bufnr, nil)
|
||||
end
|
||||
end
|
||||
|
||||
return buf_storage
|
||||
387
.config/nvim/pack/tree/start/nui.nvim/lua/nui/utils/init.lua
Normal file
387
.config/nvim/pack/tree/start/nui.nvim/lua/nui/utils/init.lua
Normal file
|
|
@ -0,0 +1,387 @@
|
|||
local ok_nvim_version, nvim_version = pcall(vim.version)
|
||||
if not ok_nvim_version then
|
||||
nvim_version = {}
|
||||
end
|
||||
|
||||
-- internal utils
|
||||
local _ = {
|
||||
feature = {
|
||||
lua_keymap = type(vim.keymap) ~= "nil",
|
||||
lua_autocmd = type(vim.api.nvim_create_autocmd) ~= "nil",
|
||||
v0_10 = nvim_version.minor >= 10,
|
||||
v0_11 = nvim_version.minor >= 11,
|
||||
},
|
||||
}
|
||||
|
||||
local utils = {
|
||||
_ = _,
|
||||
}
|
||||
|
||||
function utils.get_editor_size()
|
||||
return {
|
||||
width = vim.o.columns,
|
||||
height = vim.o.lines,
|
||||
}
|
||||
end
|
||||
|
||||
function utils.get_window_size(winid)
|
||||
winid = winid or 0
|
||||
return {
|
||||
width = vim.api.nvim_win_get_width(winid),
|
||||
height = vim.api.nvim_win_get_height(winid),
|
||||
}
|
||||
end
|
||||
|
||||
function utils.defaults(v, default_value)
|
||||
return type(v) == "nil" and default_value or v
|
||||
end
|
||||
|
||||
-- luacheck: push no max comment line length
|
||||
---@param type_name "'nil'" | "'number'" | "'string'" | "'boolean'" | "'table'" | "'function'" | "'thread'" | "'userdata'" | "'list'" | '"map"'
|
||||
---@return boolean
|
||||
function utils.is_type(type_name, v)
|
||||
-- `vim.tbl_islist` will be removed in the future
|
||||
local islist = vim.islist or vim.tbl_islist
|
||||
if type_name == "list" then
|
||||
return islist(v)
|
||||
end
|
||||
|
||||
if type_name == "map" then
|
||||
return type(v) == "table" and not islist(v)
|
||||
end
|
||||
|
||||
return type(v) == type_name
|
||||
end
|
||||
-- luacheck: pop
|
||||
|
||||
---@param v string | number
|
||||
function utils.parse_number_input(v)
|
||||
local parsed = {}
|
||||
|
||||
parsed.is_percentage = type(v) == "string" and string.sub(v, -1) == "%"
|
||||
|
||||
if parsed.is_percentage then
|
||||
parsed.value = tonumber(string.sub(v, 1, #v - 1)) / 100
|
||||
else
|
||||
parsed.value = tonumber(v)
|
||||
parsed.is_percentage = parsed.value and 0 < parsed.value and parsed.value < 1
|
||||
end
|
||||
|
||||
return parsed
|
||||
end
|
||||
|
||||
---@param prefix? string
|
||||
---@return (fun(): string) get_next_id
|
||||
local function get_id_generator(prefix)
|
||||
prefix = prefix or ""
|
||||
local id = 0
|
||||
return function()
|
||||
id = id + 1
|
||||
return prefix .. id
|
||||
end
|
||||
end
|
||||
|
||||
_.get_next_id = get_id_generator("nui_")
|
||||
|
||||
---@private
|
||||
---@param bufnr number
|
||||
---@param linenr number line number (1-indexed)
|
||||
---@param char_start number start character position (0-indexed)
|
||||
---@param char_end number end character position (0-indexed)
|
||||
---@return number[] byte_range
|
||||
function _.char_to_byte_range(bufnr, linenr, char_start, char_end)
|
||||
local line = vim.api.nvim_buf_get_lines(bufnr, linenr - 1, linenr, false)[1]
|
||||
local skipped_part = vim.fn.strcharpart(line, 0, char_start)
|
||||
local target_part = vim.fn.strcharpart(line, char_start, char_end - char_start)
|
||||
|
||||
local byte_start = vim.fn.strlen(skipped_part)
|
||||
local byte_end = math.min(byte_start + vim.fn.strlen(target_part), vim.fn.strlen(line))
|
||||
return { byte_start, byte_end }
|
||||
end
|
||||
|
||||
---@type integer
|
||||
local fallback_namespace_id = vim.api.nvim_create_namespace("nui.nvim")
|
||||
|
||||
---@private
|
||||
---@param ns_id integer
|
||||
---@return integer
|
||||
function _.ensure_namespace_id(ns_id)
|
||||
return ns_id == -1 and fallback_namespace_id or ns_id
|
||||
end
|
||||
|
||||
---@private
|
||||
---@param ns_id? integer|string
|
||||
---@return integer ns_id namespace id
|
||||
function _.normalize_namespace_id(ns_id)
|
||||
if utils.is_type("string", ns_id) then
|
||||
---@cast ns_id string
|
||||
return vim.api.nvim_create_namespace(ns_id)
|
||||
end
|
||||
---@cast ns_id integer
|
||||
return ns_id or fallback_namespace_id
|
||||
end
|
||||
|
||||
---@private
|
||||
---@param bufnr integer
|
||||
---@param ns_id integer
|
||||
---@param linenr_start? integer (1-indexed)
|
||||
---@param linenr_end? integer (1-indexed,inclusive)
|
||||
function _.clear_namespace(bufnr, ns_id, linenr_start, linenr_end)
|
||||
linenr_start = linenr_start or 1
|
||||
linenr_end = linenr_end and linenr_end + 1 or 0
|
||||
vim.api.nvim_buf_clear_namespace(bufnr, ns_id, linenr_start - 1, linenr_end - 1)
|
||||
end
|
||||
|
||||
-- luacov: disable
|
||||
local nvim_buf_set_option = vim.api.nvim_buf_set_option
|
||||
---@param bufnr integer
|
||||
---@param name string
|
||||
---@param value any
|
||||
local function set_buf_option(bufnr, name, value)
|
||||
nvim_buf_set_option(bufnr, name, value)
|
||||
end
|
||||
|
||||
local nvim_win_set_option = vim.api.nvim_win_set_option
|
||||
---@param winid integer
|
||||
---@param name string
|
||||
---@param value any
|
||||
local function set_win_option(winid, name, value)
|
||||
nvim_win_set_option(winid, name, value)
|
||||
end
|
||||
-- luacov: enable
|
||||
|
||||
if _.feature.v0_10 then
|
||||
function set_buf_option(bufnr, name, value)
|
||||
vim.api.nvim_set_option_value(name, value, { buf = bufnr })
|
||||
end
|
||||
|
||||
function set_win_option(winid, name, value)
|
||||
vim.api.nvim_set_option_value(name, value, { win = winid, scope = "local" })
|
||||
end
|
||||
end
|
||||
|
||||
_.set_buf_option = set_buf_option
|
||||
_.set_win_option = set_win_option
|
||||
|
||||
---@private
|
||||
---@param bufnr number
|
||||
---@param buf_options table<string, any>
|
||||
function _.set_buf_options(bufnr, buf_options)
|
||||
for name, value in pairs(buf_options) do
|
||||
set_buf_option(bufnr, name, value)
|
||||
end
|
||||
end
|
||||
|
||||
---@private
|
||||
---@param winid number
|
||||
---@param win_options table<string, any>
|
||||
function _.set_win_options(winid, win_options)
|
||||
for name, value in pairs(win_options) do
|
||||
set_win_option(winid, name, value)
|
||||
end
|
||||
end
|
||||
|
||||
---@private
|
||||
---@param dimension number | string
|
||||
---@param container_dimension number
|
||||
---@return nil | number
|
||||
function _.normalize_dimension(dimension, container_dimension)
|
||||
local number = utils.parse_number_input(dimension)
|
||||
|
||||
if not number.value then
|
||||
return nil
|
||||
end
|
||||
|
||||
if number.is_percentage then
|
||||
return math.floor(container_dimension * number.value)
|
||||
end
|
||||
|
||||
return number.value
|
||||
end
|
||||
|
||||
local strchars, strcharpart, strdisplaywidth = vim.fn.strchars, vim.fn.strcharpart, vim.fn.strdisplaywidth
|
||||
|
||||
---@param text string
|
||||
---@param max_length number
|
||||
---@return string
|
||||
function _.truncate_text(text, max_length)
|
||||
if strdisplaywidth(text) <= max_length then
|
||||
return text
|
||||
end
|
||||
|
||||
local low, high = 0, strchars(text)
|
||||
local mid
|
||||
|
||||
while low < high do
|
||||
mid = math.floor((low + high + 1) / 2)
|
||||
if strdisplaywidth(strcharpart(text, 0, mid)) < max_length then
|
||||
low = mid
|
||||
else
|
||||
high = mid - 1
|
||||
end
|
||||
end
|
||||
|
||||
return strcharpart(text, 0, low) .. "…"
|
||||
end
|
||||
|
||||
---@param text NuiText
|
||||
---@param max_width number
|
||||
function _.truncate_nui_text(text, max_width)
|
||||
text:set(_.truncate_text(text:content(), max_width))
|
||||
end
|
||||
|
||||
---@param line NuiLine
|
||||
---@param max_width number
|
||||
function _.truncate_nui_line(line, max_width)
|
||||
local width = line:width()
|
||||
local last_part_idx = #line._texts
|
||||
|
||||
while width > max_width do
|
||||
local extra_width = width - max_width
|
||||
local last_part = line._texts[last_part_idx]
|
||||
|
||||
if last_part:width() <= extra_width then
|
||||
width = width - last_part:width()
|
||||
line._texts[last_part_idx] = nil
|
||||
last_part_idx = last_part_idx - 1
|
||||
|
||||
-- need to add truncate indicator in previous part
|
||||
if last_part:width() == extra_width then
|
||||
last_part = line._texts[last_part_idx]
|
||||
last_part:set(_.truncate_text(last_part:content() .. " ", last_part:width()))
|
||||
end
|
||||
else
|
||||
last_part:set(_.truncate_text(last_part:content(), last_part:width() - extra_width))
|
||||
width = width - extra_width
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
---@param align "'left'" | "'center'" | "'right'"
|
||||
---@param total_width number
|
||||
---@param text_width number
|
||||
---@return number left_gap_width, number right_gap_width
|
||||
function _.calculate_gap_width(align, total_width, text_width)
|
||||
local gap_width = total_width - text_width
|
||||
if align == "left" then
|
||||
return 0, gap_width
|
||||
elseif align == "center" then
|
||||
return math.floor(gap_width / 2), math.ceil(gap_width / 2)
|
||||
elseif align == "right" then
|
||||
return gap_width, 0
|
||||
end
|
||||
|
||||
error("invalid value align=" .. align)
|
||||
end
|
||||
|
||||
---@param lines (string|NuiLine)[]
|
||||
---@param bufnr number
|
||||
---@param ns_id number
|
||||
---@param linenr_start integer (1-indexed)
|
||||
---@param linenr_end? integer (1-indexed,inclusive)
|
||||
---@param byte_start? integer (0-indexed)
|
||||
---@param byte_end? integer (0-indexed,exclusive)
|
||||
function _.render_lines(lines, bufnr, ns_id, linenr_start, linenr_end, byte_start, byte_end)
|
||||
local row_start = linenr_start - 1
|
||||
local row_end = linenr_end or row_start + 1
|
||||
|
||||
local content = vim.tbl_map(function(line)
|
||||
if type(line) == "string" then
|
||||
return line
|
||||
end
|
||||
return line:content()
|
||||
end, lines)
|
||||
|
||||
if byte_start then
|
||||
local col_start = byte_start
|
||||
local col_end = byte_end or #vim.api.nvim_buf_get_lines(bufnr, row_start, row_end, false)[1]
|
||||
vim.api.nvim_buf_set_text(bufnr, row_start, col_start, row_end - 1, col_end, content)
|
||||
else
|
||||
vim.api.nvim_buf_set_lines(bufnr, row_start, row_end, false, content)
|
||||
end
|
||||
|
||||
for linenr, line in ipairs(lines) do
|
||||
if type(line) ~= "string" then
|
||||
line:highlight(bufnr, ns_id, linenr + row_start, byte_start)
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
---@param bufnr integer
|
||||
---@param linenr_start integer (1-indexed)
|
||||
---@param linenr_end integer (1-indexed,inclusive)
|
||||
function _.clear_lines(bufnr, linenr_start, linenr_end)
|
||||
local count = linenr_end - linenr_start + 1
|
||||
if count < 1 then
|
||||
return
|
||||
end
|
||||
|
||||
local lines = {}
|
||||
for i = 1, count do
|
||||
lines[i] = ""
|
||||
end
|
||||
|
||||
vim.api.nvim_buf_set_lines(bufnr, linenr_start - 1, linenr_end, false, lines)
|
||||
end
|
||||
|
||||
function _.normalize_layout_options(options)
|
||||
if utils.is_type("string", options.relative) then
|
||||
options.relative = {
|
||||
type = options.relative,
|
||||
}
|
||||
end
|
||||
|
||||
if options.position and not utils.is_type("table", options.position) then
|
||||
options.position = {
|
||||
row = options.position,
|
||||
col = options.position,
|
||||
}
|
||||
end
|
||||
|
||||
if options.size and not utils.is_type("table", options.size) then
|
||||
options.size = {
|
||||
width = options.size,
|
||||
height = options.size,
|
||||
}
|
||||
end
|
||||
|
||||
return options
|
||||
end
|
||||
|
||||
---@param winhighlight string
|
||||
---@return table<string, string> highlight_map
|
||||
function _.parse_winhighlight(winhighlight)
|
||||
local highlight = {}
|
||||
local parts = vim.split(winhighlight, ",", { plain = true, trimempty = true })
|
||||
for _, part in ipairs(parts) do
|
||||
local key, value = part:match("(.+):(.+)")
|
||||
highlight[key] = value
|
||||
end
|
||||
return highlight
|
||||
end
|
||||
|
||||
---@param highlight_map table<string, string>
|
||||
---@return string winhighlight
|
||||
function _.serialize_winhighlight(highlight_map)
|
||||
local parts = vim.tbl_map(function(key)
|
||||
return key .. ":" .. highlight_map[key]
|
||||
end, vim.tbl_keys(highlight_map))
|
||||
table.sort(parts)
|
||||
return table.concat(parts, ",")
|
||||
end
|
||||
|
||||
function _.get_default_winborder()
|
||||
return "none"
|
||||
end
|
||||
|
||||
if _.feature.v0_11 then
|
||||
function _.get_default_winborder()
|
||||
local style = vim.api.nvim_get_option_value("winborder", {})
|
||||
if style == "" then
|
||||
return "none"
|
||||
end
|
||||
return style
|
||||
end
|
||||
end
|
||||
|
||||
return utils
|
||||
154
.config/nvim/pack/tree/start/nui.nvim/lua/nui/utils/keymap.lua
Normal file
154
.config/nvim/pack/tree/start/nui.nvim/lua/nui/utils/keymap.lua
Normal file
|
|
@ -0,0 +1,154 @@
|
|||
local buf_storage = require("nui.utils.buf_storage")
|
||||
local is_type = require("nui.utils").is_type
|
||||
local feature = require("nui.utils")._.feature
|
||||
|
||||
local keymap = {
|
||||
storage = buf_storage.create("nui.utils.keymap", { _next_handler_id = 1, keys = {}, handlers = {} }),
|
||||
}
|
||||
|
||||
---@param mode string
|
||||
---@param key string
|
||||
---@return string key_id
|
||||
local function get_key_id(mode, key)
|
||||
return string.format("%s---%s", mode, vim.api.nvim_replace_termcodes(key, true, true, true))
|
||||
end
|
||||
|
||||
---@param bufnr number
|
||||
---@param key_id string
|
||||
---@return integer|nil handler_id
|
||||
local function get_handler_id(bufnr, key_id)
|
||||
return keymap.storage[bufnr].keys[key_id]
|
||||
end
|
||||
|
||||
---@param bufnr number
|
||||
---@param key_id string
|
||||
---@return integer handler_id
|
||||
local function next_handler_id(bufnr, key_id)
|
||||
local handler_id = keymap.storage[bufnr]._next_handler_id
|
||||
keymap.storage[bufnr].keys[key_id] = handler_id
|
||||
keymap.storage[bufnr]._next_handler_id = handler_id + 1
|
||||
return handler_id
|
||||
end
|
||||
|
||||
---@param bufnr number
|
||||
---@param mode string
|
||||
---@param key string
|
||||
---@param handler string|fun(): nil
|
||||
---@return { rhs: string, callback?: fun(): nil }|nil
|
||||
local function get_keymap_info(bufnr, mode, key, handler, overwrite)
|
||||
local key_id = get_key_id(mode, key)
|
||||
|
||||
-- luacov: disable
|
||||
if get_handler_id(bufnr, key_id) and not overwrite then
|
||||
return nil
|
||||
end
|
||||
-- luacov: enable
|
||||
|
||||
local handler_id = next_handler_id(bufnr, key_id)
|
||||
|
||||
local rhs, callback = "", nil
|
||||
|
||||
if type(handler) == "function" then
|
||||
if feature.lua_keymap then
|
||||
callback = handler
|
||||
else
|
||||
keymap.storage[bufnr].handlers[handler_id] = handler
|
||||
rhs = string.format("<cmd>lua require('nui.utils.keymap').execute(%s, %s)<CR>", bufnr, handler_id)
|
||||
end
|
||||
else
|
||||
rhs = handler
|
||||
end
|
||||
|
||||
return {
|
||||
rhs = rhs,
|
||||
callback = callback,
|
||||
}
|
||||
end
|
||||
|
||||
---@param bufnr number
|
||||
---@param handler_id number
|
||||
function keymap.execute(bufnr, handler_id)
|
||||
local handler = keymap.storage[bufnr].handlers[handler_id]
|
||||
if is_type("function", handler) then
|
||||
handler(bufnr)
|
||||
end
|
||||
end
|
||||
|
||||
---@param bufnr number
|
||||
---@param mode string
|
||||
---@param lhs string|string[]
|
||||
---@param handler string|fun(): nil
|
||||
---@param opts? table<"'expr'"|"'noremap'"|"'nowait'"|"'remap'"|"'script'"|"'silent'"|"'unique'", boolean>
|
||||
---@return nil
|
||||
function keymap.set(bufnr, mode, lhs, handler, opts, force)
|
||||
if feature.lua_keymap and not is_type("boolean", force) then
|
||||
force = true
|
||||
end
|
||||
|
||||
local keys = lhs
|
||||
if type(lhs) ~= "table" then
|
||||
keys = { lhs }
|
||||
end
|
||||
---@cast keys -string
|
||||
|
||||
opts = opts or {}
|
||||
|
||||
if not is_type("nil", opts.remap) then
|
||||
opts.noremap = not opts.remap
|
||||
opts.remap = nil
|
||||
end
|
||||
|
||||
for _, key in ipairs(keys) do
|
||||
local keymap_info = get_keymap_info(bufnr, mode, key, handler, force)
|
||||
-- luacov: disable
|
||||
if not keymap_info then
|
||||
return false
|
||||
end
|
||||
-- luacov: enable
|
||||
|
||||
local options = vim.deepcopy(opts)
|
||||
options.callback = keymap_info.callback
|
||||
|
||||
vim.api.nvim_buf_set_keymap(bufnr, mode, key, keymap_info.rhs, options)
|
||||
end
|
||||
|
||||
return true
|
||||
end
|
||||
|
||||
---@param bufnr number
|
||||
---@param mode string
|
||||
---@param lhs string|string[]
|
||||
---@return nil
|
||||
function keymap._del(bufnr, mode, lhs, force)
|
||||
if feature.lua_keymap and not is_type("boolean", force) then
|
||||
force = true
|
||||
end
|
||||
|
||||
local keys = lhs
|
||||
if type(lhs) ~= "table" then
|
||||
keys = { lhs }
|
||||
end
|
||||
---@cast keys -string
|
||||
|
||||
for _, key in ipairs(keys) do
|
||||
local key_id = get_key_id(mode, key)
|
||||
|
||||
local handler_id = get_handler_id(bufnr, key_id)
|
||||
|
||||
-- luacov: disable
|
||||
if not handler_id and not force then
|
||||
return false
|
||||
---@cast handler_id -nil
|
||||
end
|
||||
-- luacov: enable
|
||||
|
||||
keymap.storage[bufnr].keys[key_id] = nil
|
||||
keymap.storage[bufnr].handlers[handler_id] = nil
|
||||
|
||||
vim.api.nvim_buf_del_keymap(bufnr, mode, key)
|
||||
end
|
||||
|
||||
return true
|
||||
end
|
||||
|
||||
return keymap
|
||||
Loading…
Add table
Add a link
Reference in a new issue