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
10
.config/nvim/pack/tree/start/nui.nvim/.codecov.yml
Normal file
10
.config/nvim/pack/tree/start/nui.nvim/.codecov.yml
Normal file
|
@ -0,0 +1,10 @@
|
|||
coverage:
|
||||
status:
|
||||
project:
|
||||
default:
|
||||
informational: true
|
||||
only_pulls: true
|
||||
patch:
|
||||
default:
|
||||
informational: true
|
||||
only_pulls: true
|
123
.config/nvim/pack/tree/start/nui.nvim/.github/workflows/ci.yml
vendored
Normal file
123
.config/nvim/pack/tree/start/nui.nvim/.github/workflows/ci.yml
vendored
Normal file
|
@ -0,0 +1,123 @@
|
|||
name: CI
|
||||
|
||||
on:
|
||||
push:
|
||||
branches: [main]
|
||||
pull_request:
|
||||
branches: [main]
|
||||
|
||||
jobs:
|
||||
lint:
|
||||
name: luacheck
|
||||
runs-on: ubuntu-22.04
|
||||
steps:
|
||||
- name: Checkout
|
||||
uses: actions/checkout@v3
|
||||
- name: Get Cache Key
|
||||
id: luver-cache-key
|
||||
env:
|
||||
CI_RUNNER_OS: ${{ runner.os }}
|
||||
CI_SECRETS_CACHE_VERSION: ${{ secrets.CACHE_VERSION }}
|
||||
run: |
|
||||
echo "value=${CI_RUNNER_OS}-luver-${CI_SECRETS_CACHE_VERSION}-$(date -u +%Y-%m-%d)" >> $GITHUB_OUTPUT
|
||||
shell: bash
|
||||
- name: Setup Cache
|
||||
uses: actions/cache@v3
|
||||
with:
|
||||
path: ~/.local/share/luver
|
||||
key: ${{ steps.luver-cache-key.outputs.value }}
|
||||
- name: Setup Lua
|
||||
uses: MunifTanjim/luver-action@v1
|
||||
with:
|
||||
default: 5.1.5
|
||||
lua_versions: 5.1.5
|
||||
luarocks_versions: 5.1.5:3.8.0
|
||||
- name: Setup luacheck
|
||||
run: |
|
||||
luarocks install luacheck
|
||||
- name: Lint
|
||||
run: ./scripts/lint.sh --no-cache
|
||||
|
||||
format:
|
||||
name: stylua
|
||||
runs-on: ubuntu-22.04
|
||||
steps:
|
||||
- name: Checkout
|
||||
uses: actions/checkout@v3
|
||||
- name: Check Format
|
||||
uses: JohnnyMorganz/stylua-action@v3
|
||||
with:
|
||||
token: ${{ secrets.GITHUB_TOKEN }}
|
||||
version: 0.17.1
|
||||
args: --color always --check lua/nui/ tests/
|
||||
|
||||
test:
|
||||
name: test
|
||||
runs-on: ubuntu-22.04
|
||||
steps:
|
||||
- name: Checkout
|
||||
uses: actions/checkout@v3
|
||||
- name: Get Cache Key
|
||||
id: luver-cache-key
|
||||
env:
|
||||
CI_RUNNER_OS: ${{ runner.os }}
|
||||
CI_SECRETS_CACHE_VERSION: ${{ secrets.CACHE_VERSION }}
|
||||
run: |
|
||||
echo "value=${CI_RUNNER_OS}-luver-${CI_SECRETS_CACHE_VERSION}-$(date -u +%Y-%m-%d)" >> $GITHUB_OUTPUT
|
||||
shell: bash
|
||||
- name: Setup Cache
|
||||
uses: actions/cache@v3
|
||||
with:
|
||||
path: ~/.local/share/luver
|
||||
key: ${{ steps.luver-cache-key.outputs.value }}
|
||||
- name: Setup Lua
|
||||
uses: MunifTanjim/luver-action@v1
|
||||
with:
|
||||
default: 5.1.5
|
||||
lua_versions: 5.1.5
|
||||
luarocks_versions: 5.1.5:3.8.0
|
||||
- name: Setup luacov
|
||||
run: |
|
||||
luarocks install luacov
|
||||
luarocks install luafilesystem
|
||||
- name: Setup Neovim
|
||||
uses: MunifTanjim/setup-neovim-action@v1
|
||||
with:
|
||||
tag: nightly
|
||||
- name: Run Tests
|
||||
run: |
|
||||
nvim --version
|
||||
./scripts/test.sh
|
||||
- name: Upload coverage to Codecov
|
||||
uses: codecov/codecov-action@v3
|
||||
with:
|
||||
verbose: true
|
||||
|
||||
release:
|
||||
name: release
|
||||
if: ${{ github.ref == 'refs/heads/main' }}
|
||||
needs:
|
||||
- lint
|
||||
- format
|
||||
- test
|
||||
runs-on: ubuntu-22.04
|
||||
permissions:
|
||||
actions: write
|
||||
contents: write
|
||||
pull-requests: write
|
||||
steps:
|
||||
- uses: google-github-actions/release-please-action@v3
|
||||
id: release
|
||||
with:
|
||||
release-type: simple
|
||||
package-name: nui.nvim
|
||||
bump-minor-pre-major: true
|
||||
pull-request-title-pattern: "chore: release ${version}"
|
||||
include-v-in-tag: false
|
||||
- name: Trigger Publish
|
||||
if: ${{ steps.release.outputs.release_created }}
|
||||
env:
|
||||
GH_TOKEN: ${{ github.token }}
|
||||
TAG_NAME: ${{ steps.release.outputs.tag_name }}
|
||||
run: |
|
||||
gh workflow run --repo ${GITHUB_REPOSITORY} publish.yml -f version=${TAG_NAME}
|
33
.config/nvim/pack/tree/start/nui.nvim/.github/workflows/publish.yml
vendored
Normal file
33
.config/nvim/pack/tree/start/nui.nvim/.github/workflows/publish.yml
vendored
Normal file
|
@ -0,0 +1,33 @@
|
|||
name: Publish
|
||||
|
||||
on:
|
||||
push:
|
||||
tags:
|
||||
- "[0-1].[0-9]+.[0-9]+"
|
||||
workflow_dispatch:
|
||||
inputs:
|
||||
version:
|
||||
description: Version to publish
|
||||
required: false
|
||||
type: string
|
||||
force:
|
||||
description: Force publish
|
||||
required: false
|
||||
default: false
|
||||
type: boolean
|
||||
|
||||
jobs:
|
||||
publish:
|
||||
name: publish
|
||||
runs-on: ubuntu-22.04
|
||||
steps:
|
||||
- name: Checkout
|
||||
uses: actions/checkout@v3
|
||||
- name: LuaRocks Publish
|
||||
uses: MunifTanjim/luarocks-publish-action@v1
|
||||
with:
|
||||
lua_version: 5.1.5
|
||||
luarocks_version: 3.9.1
|
||||
version: ${{ inputs.version }}
|
||||
api_key: ${{ secrets.LUAROCKS_API_KEY }}
|
||||
force: ${{ inputs.force }}
|
5
.config/nvim/pack/tree/start/nui.nvim/.gitignore
vendored
Normal file
5
.config/nvim/pack/tree/start/nui.nvim/.gitignore
vendored
Normal file
|
@ -0,0 +1,5 @@
|
|||
.luacheckcache
|
||||
|
||||
luacov.*.out
|
||||
|
||||
.tests
|
16
.config/nvim/pack/tree/start/nui.nvim/.luacheckrc
Normal file
16
.config/nvim/pack/tree/start/nui.nvim/.luacheckrc
Normal file
|
@ -0,0 +1,16 @@
|
|||
cache = ".luacheckcache"
|
||||
-- https://luacheck.readthedocs.io/en/stable/warnings.html
|
||||
ignore = {
|
||||
"211/_.*",
|
||||
"212/_.*",
|
||||
"213/_.*",
|
||||
}
|
||||
include_files = { "*.luacheckrc", "lua/**/*.lua", "tests/**/*.lua" }
|
||||
globals = { "vim" }
|
||||
std = "luajit"
|
||||
|
||||
files["tests/helpers/**/*.lua"] = {
|
||||
read_globals = { "assert", "describe" },
|
||||
}
|
||||
|
||||
-- vim: set filetype=lua :
|
6
.config/nvim/pack/tree/start/nui.nvim/.luacov
Normal file
6
.config/nvim/pack/tree/start/nui.nvim/.luacov
Normal file
|
@ -0,0 +1,6 @@
|
|||
include = {
|
||||
"lua%/nui",
|
||||
}
|
||||
includeuntestedfiles = {
|
||||
"lua/nui"
|
||||
}
|
6
.config/nvim/pack/tree/start/nui.nvim/.stylua.toml
Normal file
6
.config/nvim/pack/tree/start/nui.nvim/.stylua.toml
Normal file
|
@ -0,0 +1,6 @@
|
|||
column_width = 120
|
||||
line_endings = "Unix"
|
||||
indent_type = "Spaces"
|
||||
indent_width = 2
|
||||
quote_style = "AutoPreferDouble"
|
||||
no_call_parentheses = false
|
331
.config/nvim/pack/tree/start/nui.nvim/CHANGELOG.md
Normal file
331
.config/nvim/pack/tree/start/nui.nvim/CHANGELOG.md
Normal file
|
@ -0,0 +1,331 @@
|
|||
# Changelog
|
||||
|
||||
## [0.4.0](https://github.com/MunifTanjim/nui.nvim/compare/0.3.0...0.4.0) (2025-04-23)
|
||||
|
||||
|
||||
### Features
|
||||
|
||||
* **popup:** support 'default' border style ([8d5b0b5](https://github.com/MunifTanjim/nui.nvim/commit/8d5b0b568517935d3c84f257f272ef004d9f5a59))
|
||||
* **popup:** use 'winborder' for default border ([118a12f](https://github.com/MunifTanjim/nui.nvim/commit/118a12f6304759d95d0d003f64067d93572b3238))
|
||||
* **popup:** use same zindex for border ([a2bc1e9](https://github.com/MunifTanjim/nui.nvim/commit/a2bc1e9d0359caa5d11ad967cd1e30e8d4676226))
|
||||
* **table:** accept param 'position' for method 'get_cell' ([8794284](https://github.com/MunifTanjim/nui.nvim/commit/87942848c93668532f46ec61ccc7aff7abf7d37e))
|
||||
* **table:** expose NuiTable.Cell.range ([b81333d](https://github.com/MunifTanjim/nui.nvim/commit/b81333d12f824dbed5eb231c8a4409a290fdd848))
|
||||
|
||||
|
||||
### Bug Fixes
|
||||
|
||||
* consider multi-byte characters when truncating text ([53e907f](https://github.com/MunifTanjim/nui.nvim/commit/53e907ffe5eedebdca1cd503b00aa8692068ca46))
|
||||
* **input:** cursor position patch w/ relative=cursor ([b18316d](https://github.com/MunifTanjim/nui.nvim/commit/b18316d50538bb3f40c8fd2c887490c9d29f8811))
|
||||
* **input:** cursor position patch w/ relative=editor ([a3597dc](https://github.com/MunifTanjim/nui.nvim/commit/a3597dc88b53489d3fddbddbbd13787355253bb0))
|
||||
* **input:** ignore 'keymap' for feeding default value ([fbb139c](https://github.com/MunifTanjim/nui.nvim/commit/fbb139c6f14896b434d0229099e1acd863ae6bec))
|
||||
* **input:** mounting multiple inputs together ([322978c](https://github.com/MunifTanjim/nui.nvim/commit/322978c734866996274467de084a95e4f9b5e0b1))
|
||||
* **layout:** enable nested flag for WinClosed autocmd ([fc59553](https://github.com/MunifTanjim/nui.nvim/commit/fc59553b5a8a1c13b8aa25ae62b6a47ec2b1882c))
|
||||
* **layout:** handle if child.grow results in height <= 0 ([61574ce](https://github.com/MunifTanjim/nui.nvim/commit/61574ce6e60c815b0a0c4b5655b8486ba58089a1))
|
||||
* **layout:** immediate hide/unmount after mounting ([42e4756](https://github.com/MunifTanjim/nui.nvim/commit/42e47565ecbd22306205904e21b45c169812525c))
|
||||
* **layout:** starting current position for nested boxes ([1b24de4](https://github.com/MunifTanjim/nui.nvim/commit/1b24de4778de527ef82adad6d0e819819d946387))
|
||||
* **popup:** always use current window for relative=cursor ([3dc46d7](https://github.com/MunifTanjim/nui.nvim/commit/3dc46d725f7b94bee5117c0a699b57b1902b5d65))
|
||||
* **popup:** ignore 'winborder' for complex border ([aa29efe](https://github.com/MunifTanjim/nui.nvim/commit/aa29efe58f2e5734ff49b44c3d7d0cd4b9266e9a))
|
||||
* **popup:** mimic native 'solid' border ([cbd2668](https://github.com/MunifTanjim/nui.nvim/commit/cbd2668414331c10039278f558630ed19b93e69b))
|
||||
* **tree:** update error message for duplicate node id ([8d3bce9](https://github.com/MunifTanjim/nui.nvim/commit/8d3bce9764e627b62b07424e0df77f680d47ffdb))
|
||||
|
||||
## [0.3.0](https://github.com/MunifTanjim/nui.nvim/compare/0.2.0...0.3.0) (2024-02-16)
|
||||
|
||||
|
||||
### Features
|
||||
|
||||
* **layout:** method 'show' mounts if not already mounted ([c1627d0](https://github.com/MunifTanjim/nui.nvim/commit/c1627d07dbd64fac9bab213e30df7ec044ac32c6))
|
||||
* **popup:** method 'show' mounts if not already mounted ([ecd77d8](https://github.com/MunifTanjim/nui.nvim/commit/ecd77d8b5d917714f4e4f7bf5b7e91184c6cecae))
|
||||
* **split:** method 'show' mounts if not already mounted ([43f7605](https://github.com/MunifTanjim/nui.nvim/commit/43f7605f864d82ab1a2642541814465c25fb76d8))
|
||||
* support decimal number in (0,1) range as size ([35da9ca](https://github.com/MunifTanjim/nui.nvim/commit/35da9ca1de0fc4dda96c2e214d93d363c145f418))
|
||||
* **tree:** allow node:expand with zero child ([0f913a3](https://github.com/MunifTanjim/nui.nvim/commit/0f913a3ae1a24c8a4487fbf111b4044cc22b1b0d))
|
||||
* **utils:** replace deprecated api to set option ([401a7c6](https://github.com/MunifTanjim/nui.nvim/commit/401a7c65bfd6433e1b0b48d2c246e2621fc44387))
|
||||
|
||||
|
||||
### Bug Fixes
|
||||
|
||||
* **input:** check mounted state in mount/unmount method ([c0c8e34](https://github.com/MunifTanjim/nui.nvim/commit/c0c8e347ceac53030f5c1ece1c5a5b6a17a25b32))
|
||||
* **input:** skip cursor position patch for hidden input ([80445d0](https://github.com/MunifTanjim/nui.nvim/commit/80445d015d2b5f9af0d9e8bce63d303bc86eda8a))
|
||||
* **menu:** defer execution of `make_default_prepare_node` ([49182fa](https://github.com/MunifTanjim/nui.nvim/commit/49182fae69bd3f9be33862f106c1bb9f6bc3b4f5))
|
||||
* **menu:** set default zindex higher than popup ([abb0662](https://github.com/MunifTanjim/nui.nvim/commit/abb066278507040e4c1ddf1c53ccde3139b42ab0))
|
||||
* **popup:** make sure border buf is modifiable ([aa1b4c1](https://github.com/MunifTanjim/nui.nvim/commit/aa1b4c1e05983ff7debd2b4b2788651db099de2f))
|
||||
* **popup:** support border:set_text before layout mount ([c9b4de6](https://github.com/MunifTanjim/nui.nvim/commit/c9b4de623d19a85b353ff70d2ae9c77143abe69c))
|
||||
* **popup:** use popup winblend for border ([257dccc](https://github.com/MunifTanjim/nui.nvim/commit/257dccc43b4badc735978f0791d216f7d665b75a))
|
||||
* **split:** for relative=editor always split from current window ([af8ddf5](https://github.com/MunifTanjim/nui.nvim/commit/af8ddf5db7e8485051aacefb24d76ab24ea26a0c))
|
||||
* **split:** manual doautocmd BufWinEnter ([af7dfee](https://github.com/MunifTanjim/nui.nvim/commit/af7dfee12fbf51d12cfc6ee386fa54f7a5a573c8))
|
||||
|
||||
## [0.2.0](https://github.com/MunifTanjim/nui.nvim/compare/0.1.0...0.2.0) (2023-06-18)
|
||||
|
||||
|
||||
### Features
|
||||
|
||||
* **input:** call on_close inside :unmount ([60e91dd](https://github.com/MunifTanjim/nui.nvim/commit/60e91dd3d3b19dfe1ca5833600dd94381657f035))
|
||||
* **input:** update types ([c3c2957](https://github.com/MunifTanjim/nui.nvim/commit/c3c2957f2d5e2d5ebeb1be421cb0a2f4b50461ff))
|
||||
* **layout:** support anchor for float layout ([f284153](https://github.com/MunifTanjim/nui.nvim/commit/f2841533540eb60d6878964a3cd3f8196e1f200c))
|
||||
* **layout:** update types ([62b3203](https://github.com/MunifTanjim/nui.nvim/commit/62b320361fe0c93697a600fd4ca9ede295bd3c81))
|
||||
* **line:** update types ([1a8d824](https://github.com/MunifTanjim/nui.nvim/commit/1a8d8240b458a6b82751702bfb217f00eaf305b6))
|
||||
* **menu:** update types ([0a97a88](https://github.com/MunifTanjim/nui.nvim/commit/0a97a88bf28c8545550bcbcdd28a03b428647dbc))
|
||||
* **popup:** add method border:set_style ([9d98e9b](https://github.com/MunifTanjim/nui.nvim/commit/9d98e9bac8cf681a608ef20c0a2205354a77c419))
|
||||
* **popup:** create border buffer on initialization ([643e9af](https://github.com/MunifTanjim/nui.nvim/commit/643e9afb9411f5ebd95efb43437692e74238a4a3))
|
||||
* **popup:** support `(text, hl_group)[]` for border text ([062e366](https://github.com/MunifTanjim/nui.nvim/commit/062e366afcdf2bc1e9d28313a1df4ff14f05cb4e))
|
||||
* **popup:** support anchor in :update_layout ([52c9115](https://github.com/MunifTanjim/nui.nvim/commit/52c9115b10b22a2b8416bdab3072662d67d91ed6))
|
||||
* **popup:** support nui.line for border text ([1b8fa8b](https://github.com/MunifTanjim/nui.nvim/commit/1b8fa8b2adaf3583a05f53b505093017d23cd62f))
|
||||
* **popup:** update types ([f6b6923](https://github.com/MunifTanjim/nui.nvim/commit/f6b6923883491aeb7ce8dbae3b7b3767e8376aa8))
|
||||
* **split:** update types ([f4469cb](https://github.com/MunifTanjim/nui.nvim/commit/f4469cb716ba4430e22ad7f0c71ef4da8baf1f34))
|
||||
* support byte range for _.render_lines util ([993d550](https://github.com/MunifTanjim/nui.nvim/commit/993d5500f1c09710feae07a7887cf9a36cd7e02d))
|
||||
* **table:** add nui.table block ([bfd3806](https://github.com/MunifTanjim/nui.nvim/commit/bfd3806904c29babfa61705a37e8b32ab687d2d2))
|
||||
* **table:** support linenr_start for render method ([457a5cf](https://github.com/MunifTanjim/nui.nvim/commit/457a5cfe43a18d21337045b6026818a2898144d1))
|
||||
* **table:** update types ([64bdc57](https://github.com/MunifTanjim/nui.nvim/commit/64bdc579873fa5bd303f6951ead2b419493c88e8))
|
||||
* **text:** update types ([9f7666d](https://github.com/MunifTanjim/nui.nvim/commit/9f7666d89f9b4abf76d7db25a0511833dc72e7c1))
|
||||
* **tree:** always track linenr in :render ([f008972](https://github.com/MunifTanjim/nui.nvim/commit/f008972ac7d24f7188521a7f8d158aac2fb0b07e))
|
||||
* **tree:** update types ([d3cc976](https://github.com/MunifTanjim/nui.nvim/commit/d3cc9762581afa19e86353238423f521a61aeea4))
|
||||
* **utils:** support string line in _.render_lines ([51764d2](https://github.com/MunifTanjim/nui.nvim/commit/51764d2c2235ad944ef4086d5f3954305728e5bd))
|
||||
* **utils:** update types ([df321ba](https://github.com/MunifTanjim/nui.nvim/commit/df321ba052f37715ea5936a52d862a12efd836c6))
|
||||
|
||||
|
||||
### Bug Fixes
|
||||
|
||||
* **input:** unmount race condition ([e319f25](https://github.com/MunifTanjim/nui.nvim/commit/e319f2554d14a521f4271576ebff2685105d7628))
|
||||
* **layout:** even more patch for neovim/neovim[#18925](https://github.com/MunifTanjim/nui.nvim/issues/18925) ([de66444](https://github.com/MunifTanjim/nui.nvim/commit/de6644476702ba39344f1b28900b74381bc8c4c9))
|
||||
* **layout:** more robust workaround for neovim/neovim[#18925](https://github.com/MunifTanjim/nui.nvim/issues/18925) ([9230eb0](https://github.com/MunifTanjim/nui.nvim/commit/9230eb01fb34f81b7b31ac77dcfdf356a71e487e))
|
||||
* **popup:** border empty char handling ([bd2fefb](https://github.com/MunifTanjim/nui.nvim/commit/bd2fefb2efac70231fee497137295333dd4ada30))
|
||||
* **popup:** ensure valid border.bufnr on mount ([6867305](https://github.com/MunifTanjim/nui.nvim/commit/6867305a508e374b1f4ec84ba0efa59351c6f7e6))
|
||||
* **table:** update types ([d5a82aa](https://github.com/MunifTanjim/nui.nvim/commit/d5a82aae64426a805e19d8ef5a379292f9dc55d3))
|
||||
* **tree:** do not wipe linenr tracking in :set_nodes ([da3b5eb](https://github.com/MunifTanjim/nui.nvim/commit/da3b5eb197391f3e1fac6f79c75e123ae9590be3))
|
||||
|
||||
|
||||
### Performance Improvements
|
||||
|
||||
* **tree:** optimize hot paths ([19de4d5](https://github.com/MunifTanjim/nui.nvim/commit/19de4d5299be40a9aada6af940daeef20a59929e))
|
||||
|
||||
## 0.1.0 (2023-05-27)
|
||||
|
||||
|
||||
### ⚠ BREAKING CHANGES
|
||||
|
||||
* **line:** change parameter order for methods.
|
||||
* **text:** change parameter order for methods.
|
||||
|
||||
### Features
|
||||
|
||||
* accept multiple keys for keymap ([4998347](https://github.com/MunifTanjim/nui.nvim/commit/4998347f02fde115ad0c023b90be9c5654834635))
|
||||
* add internal utils._.calculate_gap_width ([90d7285](https://github.com/MunifTanjim/nui.nvim/commit/90d7285c182b396c21ba69684068fd7d5d6eba79))
|
||||
* add util to clear namespace for buffer ([6c63bf5](https://github.com/MunifTanjim/nui.nvim/commit/6c63bf5fd51076455d1b04f5c09d9841ab081263))
|
||||
* **bar:** add lower-level `core.add_highlight` function ([20385a6](https://github.com/MunifTanjim/nui.nvim/commit/20385a698e8a5dd98ee7e63f16b700a10b921098))
|
||||
* **bar:** add some lower-level `core.add_*` functions ([5d1ca66](https://github.com/MunifTanjim/nui.nvim/commit/5d1ca66829d8fac9965cd18fcc2cd9aa49ba1ea5))
|
||||
* **bar:** initial implementation ([35758e9](https://github.com/MunifTanjim/nui.nvim/commit/35758e946a64376e0e9625a27469410b3d1f9223))
|
||||
* **bar:** remove module ([0dc148c](https://github.com/MunifTanjim/nui.nvim/commit/0dc148c6ec06577fcf06cbab3b7dac96d48ba6be))
|
||||
* **input:** add component Input ([7307c94](https://github.com/MunifTanjim/nui.nvim/commit/7307c94a7a0954f1c717e094c39772f28bb9d13f))
|
||||
* **input:** move internal default_value and prompt prop ([595a2ea](https://github.com/MunifTanjim/nui.nvim/commit/595a2ea90f7d31bc23386ba67939117d45771035))
|
||||
* **input:** support nui.text for options.prompt ([c380563](https://github.com/MunifTanjim/nui.nvim/commit/c38056355f5a72c7a1a005f7125afd62ed7b2083))
|
||||
* **layout:** add method layout:update ([3f611a6](https://github.com/MunifTanjim/nui.nvim/commit/3f611a674254370d6d73f9a35a3eedbc8d07ee3b))
|
||||
* **layout:** add some util for size and position ([cc3b970](https://github.com/MunifTanjim/nui.nvim/commit/cc3b970c1537de9562f82e5fa6aa126a629242d1))
|
||||
* **layout:** feature guard lua autocmd api usage ([3dc6b89](https://github.com/MunifTanjim/nui.nvim/commit/3dc6b89bda8f1616a2638727eeb75a4a770e1c21))
|
||||
* **layout:** initial implementation ([716c3f9](https://github.com/MunifTanjim/nui.nvim/commit/716c3f9d857b62a086b4f9123d0d34d1b7733922))
|
||||
* **layout:** introduce layout type ([a5fd005](https://github.com/MunifTanjim/nui.nvim/commit/a5fd005263d238d2fbd6ee335e06139645f11fa9))
|
||||
* **layout:** introduce split layout ([042cceb](https://github.com/MunifTanjim/nui.nvim/commit/042cceb497cc4cfa3ae735a5e7bc01b4b6f19ef1))
|
||||
* **layout:** make window transparent ([2d33512](https://github.com/MunifTanjim/nui.nvim/commit/2d33512836cb603d58cb538e716a02c44547c6ab))
|
||||
* **layout:** re-use windows for unchanged split layout box ([71ddaaf](https://github.com/MunifTanjim/nui.nvim/commit/71ddaafadfd8b3c21f809ea26fb4fe2a110bbf54))
|
||||
* **layout:** support :show and :hide for float layout ([f572782](https://github.com/MunifTanjim/nui.nvim/commit/f572782bd8cc6b6b8671566b09146c4c09f20ae2))
|
||||
* **layout:** support container component ([5bc7376](https://github.com/MunifTanjim/nui.nvim/commit/5bc737602e231111c44458c84e3eca05393f355f))
|
||||
* **layout:** support o.grow factor for layout.box ([796dc82](https://github.com/MunifTanjim/nui.nvim/commit/796dc8293be59acc0c0d53b99e7dca7c32db4413))
|
||||
* **layout:** support o.grow for layout.box ([2c6bac9](https://github.com/MunifTanjim/nui.nvim/commit/2c6bac942e3af35eb42165c923c0d3dc60a46430))
|
||||
* **layout:** throw for empty box at init ([6e872b3](https://github.com/MunifTanjim/nui.nvim/commit/6e872b3bf28aaf52fd1b831ea0690aea205d6464))
|
||||
* **layout:** tweak component wire up ([eed888e](https://github.com/MunifTanjim/nui.nvim/commit/eed888e47fa91980ce295dd347b213a102200b5a))
|
||||
* **layout:** use backported autocmd methods ([4e21085](https://github.com/MunifTanjim/nui.nvim/commit/4e21085cd1b6be12da7ff0e7168e9100c51c363a))
|
||||
* **layout:** wire up float layout components ([3aa617d](https://github.com/MunifTanjim/nui.nvim/commit/3aa617d9054e052cde68cd3141db4396af93a9cb))
|
||||
* **line:** accept initial nui.text objects ([2f44cc9](https://github.com/MunifTanjim/nui.nvim/commit/2f44cc941cf1a129b6b6069a7489672b24cf0015))
|
||||
* **line:** accept NuiText object as param for line:append() method ([34fd4bf](https://github.com/MunifTanjim/nui.nvim/commit/34fd4bfde84ff4b735a98dfb3508280d39e520f6))
|
||||
* **line:** add method :width ([80122e5](https://github.com/MunifTanjim/nui.nvim/commit/80122e542fcebc361c4ca585be40a071c6e360be))
|
||||
* **line:** add nui.line block ([1333fd0](https://github.com/MunifTanjim/nui.nvim/commit/1333fd07c57310d1421dcb337a21fbddb20d3c84))
|
||||
* **line:** make ns_id required, update method signature ([5695bde](https://github.com/MunifTanjim/nui.nvim/commit/5695bde7ac9b8bcbb1df237a05169bad99458090))
|
||||
* **line:** support nui.line in method :append ([401a69f](https://github.com/MunifTanjim/nui.nvim/commit/401a69f27ede2d5a9a725f4f45758708d3b72d09))
|
||||
* make components extendable ([c75976e](https://github.com/MunifTanjim/nui.nvim/commit/c75976e823085218aeef4f5312a8c88e7d333358))
|
||||
* **menu:** add component Menu ([dca0630](https://github.com/MunifTanjim/nui.nvim/commit/dca0630b0d7ab5dfcabaa70284c94c2e133b8200))
|
||||
* **menu:** expose .tree ([d12a697](https://github.com/MunifTanjim/nui.nvim/commit/d12a6977846b2fa978bff89b439e509320854e10))
|
||||
* **menu:** improve menu separator implementation ([0e05425](https://github.com/MunifTanjim/nui.nvim/commit/0e0542505369861fc22f6bff3ad2976bdef6d8f3))
|
||||
* **menu:** move internal props ([0373a94](https://github.com/MunifTanjim/nui.nvim/commit/0373a94bc79725726aadcc1e0e26de159e3152e1))
|
||||
* **menu:** pass self to on_change callback ([4438c5e](https://github.com/MunifTanjim/nui.nvim/commit/4438c5e53f8a5c834569309a5d3e276e6bf1abe9))
|
||||
* **menu:** rename method menu:init to menu:new ([2825c3d](https://github.com/MunifTanjim/nui.nvim/commit/2825c3d60438bbed9c04f18f030ab3505249e8a7))
|
||||
* **menu:** simplify automatic width calculation ([54cbaf5](https://github.com/MunifTanjim/nui.nvim/commit/54cbaf5d227cfedbb939d16b405116e4cea6e3fb))
|
||||
* **menu:** support arbritary props for Menu.item ([77cefa6](https://github.com/MunifTanjim/nui.nvim/commit/77cefa67df7f282db20aa9afb5925aff79455dc2))
|
||||
* **menu:** support nui.line for Menu.item ([51cbd0c](https://github.com/MunifTanjim/nui.nvim/commit/51cbd0ccc9410e317a947eea1e99966226a5f8b5))
|
||||
* **menu:** support nui.text as separator char ([3029554](https://github.com/MunifTanjim/nui.nvim/commit/30295541c1bda2ab171ab7b684f790be3fbb60a7))
|
||||
* **menu:** support nui.text for Menu.item ([13e557d](https://github.com/MunifTanjim/nui.nvim/commit/13e557d045b62efc6da87bdcc77503b5adf1db21))
|
||||
* **menu:** support options.on_change ([db06fed](https://github.com/MunifTanjim/nui.nvim/commit/db06feddf324d4c6c8763fe9fca43b9140de84a9))
|
||||
* **object:** add helper functions ([9531977](https://github.com/MunifTanjim/nui.nvim/commit/95319774b31558479c71e9b190870aae7bd8e49f))
|
||||
* **object:** initial implementation ([194837f](https://github.com/MunifTanjim/nui.nvim/commit/194837ffc4a9c77dfcc18809c7e43a02b4d11e03))
|
||||
* **popup:** add method .border:set_highlight ([acb72b1](https://github.com/MunifTanjim/nui.nvim/commit/acb72b150c7fdf01331b08460ec07fe7a81029b6))
|
||||
* **popup:** add method popup:set_layout(config) ([006711f](https://github.com/MunifTanjim/nui.nvim/commit/006711f3ab4e8626f47b2b903759a9ac0e232fbd))
|
||||
* **popup:** add method popup:set_position(position, relative) ([7ea1a6b](https://github.com/MunifTanjim/nui.nvim/commit/7ea1a6b910b1b33fceb33068df8f31e14ccceb1e))
|
||||
* **popup:** add method popup:set_size(size) ([07b6e9a](https://github.com/MunifTanjim/nui.nvim/commit/07b6e9a90b58af8ca9c09f3695aa14603c947b61))
|
||||
* **popup:** add method popup:unmap ([46bbf33](https://github.com/MunifTanjim/nui.nvim/commit/46bbf336e9068c73c4e810cb93c7a1f8197ebbc3))
|
||||
* **popup:** add method popup.border:set_text(...) ([b82a5d3](https://github.com/MunifTanjim/nui.nvim/commit/b82a5d3bda43b5cd9b9dca39b782e86d2735933f))
|
||||
* **popup:** add methods popup:hide() and popup:show() ([b3c706d](https://github.com/MunifTanjim/nui.nvim/commit/b3c706d4c30bd5cd8b05d10a2c8bf8fcd1f5cf7a))
|
||||
* **popup:** add methods popup:on(...) and popup:off(...) ([0eb57a2](https://github.com/MunifTanjim/nui.nvim/commit/0eb57a2bdd565dfad09e4bda4293ef11c17d741f))
|
||||
* **popup:** add option 'focusable' ([8339965](https://github.com/MunifTanjim/nui.nvim/commit/8339965e991ad54e6606ea22213996521701e293))
|
||||
* **popup:** add option padding ([596cd77](https://github.com/MunifTanjim/nui.nvim/commit/596cd77a875eef1c6629e12074139e2ca0033e38))
|
||||
* **popup:** add options 'buf_options' and 'win_options' ([163d99a](https://github.com/MunifTanjim/nui.nvim/commit/163d99a3fb9fe9a630c96bbf6159973885c3caf3))
|
||||
* **popup:** add options.ns_id ([6f165aa](https://github.com/MunifTanjim/nui.nvim/commit/6f165aad4d2ac5a5a279a695ed247ec954cdcc4b))
|
||||
* **popup:** add type annotation for bufnr,winid,ns_id ([c2d1f73](https://github.com/MunifTanjim/nui.nvim/commit/c2d1f73eb0ab02e90e8316a6e70158d2eec72153))
|
||||
* **popup:** add type annotation for win_config ([f8ccc5c](https://github.com/MunifTanjim/nui.nvim/commit/f8ccc5cf8e8aec7ff34572b80e7b5d92b2d07556))
|
||||
* **popup:** allow layout refresh when container size changes ([ee8d315](https://github.com/MunifTanjim/nui.nvim/commit/ee8d315456691fc1d05dcec465d95f8aec902541))
|
||||
* **popup:** change behavior of padding ([9f29df4](https://github.com/MunifTanjim/nui.nvim/commit/9f29df4153da1483ad784a473f5bbcf02fcbbe3b))
|
||||
* **popup:** clear namespace object on unmount ([58e06b0](https://github.com/MunifTanjim/nui.nvim/commit/58e06b0175cf22672d96a522343ec6ce017ba54c))
|
||||
* **popup:** create buffer on initialization ([6b1deda](https://github.com/MunifTanjim/nui.nvim/commit/6b1deda411b96f0d694dead7cc12ab9db1dd9b65))
|
||||
* **popup:** default border.text hl to FloatTitle ([4eaec2a](https://github.com/MunifTanjim/nui.nvim/commit/4eaec2ac66af2ca6ddddd3f665ad0909b90ae36a))
|
||||
* **popup:** feature guard lua autocmd api usage ([6028584](https://github.com/MunifTanjim/nui.nvim/commit/60285847a4df99c2ab0ece3020eb930c81b09c41))
|
||||
* **popup:** improve border highlight implementation ([2f58c40](https://github.com/MunifTanjim/nui.nvim/commit/2f58c406eb9cb5cedf5d82f9ce7c4f6efc418550))
|
||||
* **popup:** improve cleanup ([220d4a4](https://github.com/MunifTanjim/nui.nvim/commit/220d4a4bbaa8f7dc80c0aa37e8377c7c150c6384))
|
||||
* **popup:** merge internal position_meta into position ([bc2fc9c](https://github.com/MunifTanjim/nui.nvim/commit/bc2fc9c1b7b7241c4a0f34597f8ddc11c5f90844))
|
||||
* **popup:** move internal buf_options and win_options ([3148908](https://github.com/MunifTanjim/nui.nvim/commit/31489084b7d3363c9a6f8f4d435374a25f4f636b))
|
||||
* **popup:** move internal loading and mounted state ([4dc3214](https://github.com/MunifTanjim/nui.nvim/commit/4dc321448faaad171f0aab07d092a0e626fb1cbb))
|
||||
* **popup:** move internal position state ([d229bbb](https://github.com/MunifTanjim/nui.nvim/commit/d229bbb4846bd4268ca1f6f67543294237ee0029))
|
||||
* **popup:** move internal position_meta state ([cbbbe90](https://github.com/MunifTanjim/nui.nvim/commit/cbbbe90213091a462e71a1f102e0927ac11bac8a))
|
||||
* **popup:** move internal size prop ([0a2fced](https://github.com/MunifTanjim/nui.nvim/commit/0a2fcedb97673ba478e26a0f59edbca950d15d31))
|
||||
* **popup:** move internal win_enter prop ([f7736c9](https://github.com/MunifTanjim/nui.nvim/commit/f7736c90db395adc276519bd871bcec088bed908))
|
||||
* **popup:** remove automatic cleanup ([06b48cf](https://github.com/MunifTanjim/nui.nvim/commit/06b48cf645971e1e1d598a083b98733890a82302))
|
||||
* **popup:** remove method popup:on(...) ([c24b131](https://github.com/MunifTanjim/nui.nvim/commit/c24b13195c3e8c7c42d64736d96ded0ce7cb13ed))
|
||||
* **popup:** rename method :set_layout to :update_layout ([90f59b0](https://github.com/MunifTanjim/nui.nvim/commit/90f59b035565e549e467fc414f1178d30c074ce9))
|
||||
* **popup:** rename window to popup ([7097509](https://github.com/MunifTanjim/nui.nvim/commit/7097509b4b0fd21d870cea19c51389043c408449))
|
||||
* **popup:** rework border internals ([8a776a2](https://github.com/MunifTanjim/nui.nvim/commit/8a776a2033220b16fa032b9d3590c75a178435ec))
|
||||
* **popup:** simplify border highlight mechanism ([0ec30d9](https://github.com/MunifTanjim/nui.nvim/commit/0ec30d912c473139832d5c0bfd8ccfa675a48974))
|
||||
* **popup:** simplify border.text ([674305a](https://github.com/MunifTanjim/nui.nvim/commit/674305a0cb1df2b2c0f51ade7081235755e93643))
|
||||
* **popup:** support nui.text for simple border ([5f5a2e5](https://github.com/MunifTanjim/nui.nvim/commit/5f5a2e5284a08f930098383a6d0a00b861fc58d2))
|
||||
* **popup:** support NuiText as border.text ([1248c67](https://github.com/MunifTanjim/nui.nvim/commit/1248c674a4a632ea7d45a76b1255df997379e116))
|
||||
* **popup:** support option 'anchor' ([a86c733](https://github.com/MunifTanjim/nui.nvim/commit/a86c733e7d30596d80927b5eaf6869aa9a3d30af))
|
||||
* **popup:** support set_size when unmounted ([fa86f85](https://github.com/MunifTanjim/nui.nvim/commit/fa86f8539373e1b76b93f95dfa36a5793ed4fe35))
|
||||
* **popup:** support unmanaged buffer ([335415a](https://github.com/MunifTanjim/nui.nvim/commit/335415af52ea23d07433aa1f72f7c0d56c219316))
|
||||
* **popup:** use backported autocmd methods ([372369d](https://github.com/MunifTanjim/nui.nvim/commit/372369dea4c059f831e540e34b9a49bf183c245b))
|
||||
* **split:** add component Split ([bcb7382](https://github.com/MunifTanjim/nui.nvim/commit/bcb73828d54169f5ae9d141bca05fecb6aec5ec5))
|
||||
* **split:** add method :update_layout ([32f44a6](https://github.com/MunifTanjim/nui.nvim/commit/32f44a610691ff2e22e60a8040801e047112806f))
|
||||
* **split:** add method split:unmap ([e0444e0](https://github.com/MunifTanjim/nui.nvim/commit/e0444e020fd3ed336e2b6ba998ff7903ddb68ddd))
|
||||
* **split:** create buffer on initialization ([0e36b78](https://github.com/MunifTanjim/nui.nvim/commit/0e36b78b836200ef83ef723c6a0ebb753ac8a11e))
|
||||
* **split:** feature guard lua autocmd api usage ([d14daab](https://github.com/MunifTanjim/nui.nvim/commit/d14daab6710d1ebd7ec868f15bddde2bbd2c186f))
|
||||
* **split:** improve cleanup ([36b0649](https://github.com/MunifTanjim/nui.nvim/commit/36b0649d7df3d1899d5aed8b27d22159cf058a2e))
|
||||
* **split:** move internal buf_options and win_options ([6c9a3ee](https://github.com/MunifTanjim/nui.nvim/commit/6c9a3ee9fdb2c7a48cf13d9c82ee56822c5bfdb8))
|
||||
* **split:** move internal loading and mounted state ([653199c](https://github.com/MunifTanjim/nui.nvim/commit/653199c635ad56c1e313e0890c6a72da8d3ee3bd))
|
||||
* **split:** move internal props ([25e51eb](https://github.com/MunifTanjim/nui.nvim/commit/25e51eba14cdf2d445d0c0efde78d808741483cb))
|
||||
* **split:** set buffer after .winid is set ([35091ca](https://github.com/MunifTanjim/nui.nvim/commit/35091ca0f8c766ea3542508f3ab9217a746cc5a0))
|
||||
* **split:** store id internally ([96ef1cb](https://github.com/MunifTanjim/nui.nvim/commit/96ef1cb4e3c830dc79e18acac209ea5f7eb78829))
|
||||
* **split:** support o.enter ([b75e2e6](https://github.com/MunifTanjim/nui.nvim/commit/b75e2e6d2a86a1105a756b894385d5fe836c3821))
|
||||
* **split:** support o.ns_id ([28cafab](https://github.com/MunifTanjim/nui.nvim/commit/28cafab82f5d5ffc4321b9f82bae7b38951434d1))
|
||||
* **split:** support o.relative.winid ([82851af](https://github.com/MunifTanjim/nui.nvim/commit/82851af021d651bb6bf6da0991c79f4c529ff37e))
|
||||
* **split:** tweak split size handling for new window ([b681ab2](https://github.com/MunifTanjim/nui.nvim/commit/b681ab2a8a8750b37dcece4e87105a5671c39745))
|
||||
* **split:** use backported autocmd methods ([6bd1d8a](https://github.com/MunifTanjim/nui.nvim/commit/6bd1d8af326c654a8b9d122c32a62fed5e55fe89))
|
||||
* **text:** add method :new ([4ad7811](https://github.com/MunifTanjim/nui.nvim/commit/4ad781109a44bc00fa7d1208576f4086add27ad3))
|
||||
* **text:** add method text:set(content, highlight?) ([eaf4844](https://github.com/MunifTanjim/nui.nvim/commit/eaf4844e9e84994705acc0347673a6b15e7c030f))
|
||||
* **text:** add nui.text block ([a6df800](https://github.com/MunifTanjim/nui.nvim/commit/a6df800df514c2f0a9dd88d8db267ec5b8c8d68d))
|
||||
* **text:** change highlight table key group->hl_group ([a8aaca1](https://github.com/MunifTanjim/nui.nvim/commit/a8aaca1578dff3504e78538b477910de344af0f8))
|
||||
* **text:** make ns_id required, update method signature ([b63d199](https://github.com/MunifTanjim/nui.nvim/commit/b63d199ddfdc89457ae401ed0a5659fa1055c37c))
|
||||
* **text:** preserve own extmark id ([26622d1](https://github.com/MunifTanjim/nui.nvim/commit/26622d147762f2212bf30e0792df1d0164a73cd9))
|
||||
* **text:** remove method :new ([18a0390](https://github.com/MunifTanjim/nui.nvim/commit/18a0390d5caca31b310af41787e5ddd407c68783))
|
||||
* **text:** return self from method text:set ([c5971ed](https://github.com/MunifTanjim/nui.nvim/commit/c5971ed28773cf9b6ce4ed8bfbc20266aa05b2a1))
|
||||
* **text:** support cloning and use extmarks ([281d453](https://github.com/MunifTanjim/nui.nvim/commit/281d4535d046b1dd09d45bca8b0739bb16461b75))
|
||||
* **text:** support extmark override when cloning ([878dfaf](https://github.com/MunifTanjim/nui.nvim/commit/878dfaf85fa19ff6811f19f1dad0bbfda77d5bbf))
|
||||
* **tree:** add method node:get_child_ids ([bc05620](https://github.com/MunifTanjim/nui.nvim/commit/bc056204ff06b205dd3804474fc155180e704d47))
|
||||
* **tree:** add method tree:get_nodes ([f85aedc](https://github.com/MunifTanjim/nui.nvim/commit/f85aedc1378acb375e4da0ec2220c1df0929e843))
|
||||
* **tree:** add method tree:set_nodes(nodes, parent_id?) ([b25fab5](https://github.com/MunifTanjim/nui.nvim/commit/b25fab59d997cd9793bf7f42a1e41b9b7684d987))
|
||||
* **tree:** add nui.tree block ([3bdfa78](https://github.com/MunifTanjim/nui.nvim/commit/3bdfa780fdba053358b85fd1f276052ae1455b61))
|
||||
* **tree:** add o.bufnr and deprecate o.winid ([ce4869f](https://github.com/MunifTanjim/nui.nvim/commit/ce4869f97e4f3d4f9eb64d021fff775684ae8859))
|
||||
* **tree:** clear namespace before render ([1f66cc7](https://github.com/MunifTanjim/nui.nvim/commit/1f66cc794bb6cad23f97b71e099cc32dd63c0614))
|
||||
* **tree:** make node:has_children method work before init ([96f600b](https://github.com/MunifTanjim/nui.nvim/commit/96f600b58f128bde4a78a56c0a64d55664a43955))
|
||||
* **tree:** move internal buf_options and win_options ([3a7c0c4](https://github.com/MunifTanjim/nui.nvim/commit/3a7c0c48b279ee0495c5ec61ca312fddd052195c))
|
||||
* **tree:** move internal get_node_id and prepare_node functions ([1b9e046](https://github.com/MunifTanjim/nui.nvim/commit/1b9e04685097b93a4f8d01002adaaa5dcfb327af))
|
||||
* **tree:** pass parent_node to prepare_node function ([559d33d](https://github.com/MunifTanjim/nui.nvim/commit/559d33dcace8016603129e63e5cb605aaa059c10))
|
||||
* **tree:** rename options.ns to options.ns_id ([5db3901](https://github.com/MunifTanjim/nui.nvim/commit/5db390110bf9944b678c84cd7bcd2a28af712481))
|
||||
* **tree:** return end linenr from tree:get_node method ([8ae5e31](https://github.com/MunifTanjim/nui.nvim/commit/8ae5e3106a0fa17144a0a086ccfce1e73a73f19f))
|
||||
* **tree:** return linenr from tree:get_node method ([3b746d7](https://github.com/MunifTanjim/nui.nvim/commit/3b746d7b6f16818a970d1c4810261d18b958a956))
|
||||
* **tree:** support linenr for method tree:get_node ([7fee7c6](https://github.com/MunifTanjim/nui.nvim/commit/7fee7c6c176e83806a144f7e0a8ac6251920a8a7))
|
||||
* **tree:** support linenr_start for method :render ([afb9e5b](https://github.com/MunifTanjim/nui.nvim/commit/afb9e5b4512e17d879fb069e77de4141472d0fb9))
|
||||
* **tree:** support multiline node ([4926ee9](https://github.com/MunifTanjim/nui.nvim/commit/4926ee9ba8fac49ad23cd695f1f5c0952c52dd4a))
|
||||
* **tree:** support nil return for o.prepare_node ([5a79b1b](https://github.com/MunifTanjim/nui.nvim/commit/5a79b1b3b8231cfa33290d8d3c56d36b6496499e))
|
||||
* use api-autocmd if available ([3f05d74](https://github.com/MunifTanjim/nui.nvim/commit/3f05d742b273ed4b48db4e5ab99c09b8b05cd537))
|
||||
* use native keymap callback if supported ([ad2c05c](https://github.com/MunifTanjim/nui.nvim/commit/ad2c05c983dda8d423d952fce55eb3cf3966a1ea))
|
||||
* **utils:** add autocmd ([1f51d5a](https://github.com/MunifTanjim/nui.nvim/commit/1f51d5a6735153ea5e3f9a1f68a12c9f1bce5681))
|
||||
* **utils:** add buf_storage ([6300e3b](https://github.com/MunifTanjim/nui.nvim/commit/6300e3bdcc2fc38e361a48b76247c4524a0fb27a))
|
||||
* **utils:** backport autocmd to nvim < 0.7.x ([587a49f](https://github.com/MunifTanjim/nui.nvim/commit/587a49f90fb036a6d94904851dafdd53d1327fe0))
|
||||
* **utils:** move keymap to utils ([56c2230](https://github.com/MunifTanjim/nui.nvim/commit/56c223041ad342b2a8d28d2bb1dbd88dc0d7839a))
|
||||
* **utils:** update autocmd.event ([6f9153c](https://github.com/MunifTanjim/nui.nvim/commit/6f9153cc8462a3bb0a8a883694befa6554d31403))
|
||||
* **window:** add method window:destroy() ([c51856d](https://github.com/MunifTanjim/nui.nvim/commit/c51856da53df1327a406e7983d5fd748486fc339))
|
||||
* **window:** add method window:map(...) ([f69ee04](https://github.com/MunifTanjim/nui.nvim/commit/f69ee0498ecc57268d23a4b51516002404c3def1))
|
||||
* **window:** add method window:on(event_name, handler) ([f36b496](https://github.com/MunifTanjim/nui.nvim/commit/f36b496b52aec95dc9e122830b0d2ff64eeb21f5))
|
||||
* **window:** add method window:render() ([d1a047d](https://github.com/MunifTanjim/nui.nvim/commit/d1a047d22d0794943a08a2de598ac57c61fc4978))
|
||||
* **window:** add option highlight ([c4bbe61](https://github.com/MunifTanjim/nui.nvim/commit/c4bbe6139f012aca971ea26721dc4c1942b71286))
|
||||
* **window:** change default border to none ([8db2faa](https://github.com/MunifTanjim/nui.nvim/commit/8db2faa3dfa9b196fe2fd11311c534b37db2428a))
|
||||
* **window:** enhanced border support ([a17070c](https://github.com/MunifTanjim/nui.nvim/commit/a17070c6b55ba36db82edef0c3af28ac0929e649))
|
||||
* **window:** initial implementation ([19e4bb6](https://github.com/MunifTanjim/nui.nvim/commit/19e4bb669d2fdd168a6d0a3edebaca150f93678f))
|
||||
* **window:** rename 'destroy' to 'unmount' ([c409518](https://github.com/MunifTanjim/nui.nvim/commit/c409518e6640e50dc754b199cd37246f60a3fdbc))
|
||||
* **window:** rename 'render' to 'mount' ([66190d2](https://github.com/MunifTanjim/nui.nvim/commit/66190d237fc61db2135c0c19eb9091091d9090dc))
|
||||
* **window:** simplify option relative ([5584892](https://github.com/MunifTanjim/nui.nvim/commit/55848921914083d57e178448f3714f3ba4d9fc75))
|
||||
|
||||
|
||||
### Bug Fixes
|
||||
|
||||
* **bar:** rename tabnr to tabid in context ([f220495](https://github.com/MunifTanjim/nui.nvim/commit/f2204952ba670372de507bdd446c6a14f821ac73))
|
||||
* **bar:** type for generator ([2a6533f](https://github.com/MunifTanjim/nui.nvim/commit/2a6533fb798efad7dd783311315bab8dc5eb381b))
|
||||
* **input:** escape multi-byte chars in default value ([698e758](https://github.com/MunifTanjim/nui.nvim/commit/698e75814cd7c56b0dd8af4936bcef2d13807f3c))
|
||||
* **input:** stopinsert on close ([971cca4](https://github.com/MunifTanjim/nui.nvim/commit/971cca41914aaefc9569b8a1904cf8c25cec5aa8))
|
||||
* **input:** try to keep stable cursor position on parent window ([6f803e8](https://github.com/MunifTanjim/nui.nvim/commit/6f803e88093573f73d4ee6c0dfe0575df3f97a9f))
|
||||
* **layout:** apply size for first child box in split layout ([dde3f89](https://github.com/MunifTanjim/nui.nvim/commit/dde3f89b74b726eacfa6bea82c895af265573fe4))
|
||||
* **layout:** float position calculation for child with complex border ([257da38](https://github.com/MunifTanjim/nui.nvim/commit/257da38029d3859ed111804f9d4e95b0fa993a31))
|
||||
* **layout:** focus on relative.win for split ':update' ([6e8f9a0](https://github.com/MunifTanjim/nui.nvim/commit/6e8f9a0f280fada3f8ce694a42f8376a31e9776e))
|
||||
* **layout:** o.relative for split layout ([bf5900f](https://github.com/MunifTanjim/nui.nvim/commit/bf5900f1b60bf6499755ac92315181a24a87a577))
|
||||
* **layout:** preserve split win_options 'winfixheight' and 'winfixwidth' ([ecd9def](https://github.com/MunifTanjim/nui.nvim/commit/ecd9def93891b9260b15b5fcef542eaabf4145c9))
|
||||
* **layout:** process float layout box change ([51721a4](https://github.com/MunifTanjim/nui.nvim/commit/51721a409794bf2e432e99acc21b635102fedcea))
|
||||
* **layout:** process split layout box change ([b12db53](https://github.com/MunifTanjim/nui.nvim/commit/b12db5321c194c10eb34e610fb76ce2c058853fc))
|
||||
* **layout:** typo in update_layout_config util ([d5d3d6c](https://github.com/MunifTanjim/nui.nvim/commit/d5d3d6ce542b3921f8a8da213ef7b0841f6a4adc))
|
||||
* luacheck lint warnings ([e7dd31c](https://github.com/MunifTanjim/nui.nvim/commit/e7dd31c4135389e460a686d48d10ed532ca5234e))
|
||||
* **menu:** error with fallback separator char ([76fc8ed](https://github.com/MunifTanjim/nui.nvim/commit/76fc8edf771adff74aba513bd0f62e984996ef88))
|
||||
* **popup:** add border.style default value ([a07b754](https://github.com/MunifTanjim/nui.nvim/commit/a07b754552008012f2d7d3602b7a233a29d92c66))
|
||||
* **popup:** border highlight for type:complex ([151d593](https://github.com/MunifTanjim/nui.nvim/commit/151d593b28911d61b10e1a8ba14f9e4c755141aa))
|
||||
* **popup:** border padding with style=shadow ([37e0511](https://github.com/MunifTanjim/nui.nvim/commit/37e0511f189cd19eabd0e71841f10c3a39bbb62d))
|
||||
* **popup:** check bufnr is valid before clearing namespace ([62facd3](https://github.com/MunifTanjim/nui.nvim/commit/62facd37e0dd8196212399a897374f689886f500))
|
||||
* **popup:** check if border bufnr is valid before clearing namespace ([b99e6cb](https://github.com/MunifTanjim/nui.nvim/commit/b99e6cb13dc51768abc1c4c8585045a0c0459ef1))
|
||||
* **popup:** do better mount/unmount handling ([7622fcf](https://github.com/MunifTanjim/nui.nvim/commit/7622fcf3dc4cffffc666d9f1f4e646168f640a2a))
|
||||
* **popup:** do buf_storage.cleanup after buffer wipeout ([644e595](https://github.com/MunifTanjim/nui.nvim/commit/644e595a3862ca45de71ff14fa465019ecfc17ad))
|
||||
* **popup:** do BufWinEnter autocmd after self.winid is set ([4c77e3a](https://github.com/MunifTanjim/nui.nvim/commit/4c77e3a064b7b0fdfb5f2729500a81b431ff86f8))
|
||||
* **popup:** do not reset win_options on update_layout call ([1f43b13](https://github.com/MunifTanjim/nui.nvim/commit/1f43b13d133eb4b4f53a4485379d9afa58808389))
|
||||
* **popup:** handle border padding without text ([1f9aebc](https://github.com/MunifTanjim/nui.nvim/commit/1f9aebcaca1f43c311ba16a2aad9170d597640a8))
|
||||
* **popup:** handle border:set_text properly ([e78c822](https://github.com/MunifTanjim/nui.nvim/commit/e78c822378c102978792465dbf93b8ffb27a4cc8))
|
||||
* **popup:** handle map as border.style ([26a0eea](https://github.com/MunifTanjim/nui.nvim/commit/26a0eeaca8890b74d53a91f84520abea94fcf8ee))
|
||||
* **popup:** handle various mix of border and padding ([49a155f](https://github.com/MunifTanjim/nui.nvim/commit/49a155f3bbacb90a8b2df320f5b127b568083cd6))
|
||||
* **popup:** highlight for simple border ([3ff3d26](https://github.com/MunifTanjim/nui.nvim/commit/3ff3d2628f3c14633792f702b3cfea377addcf47))
|
||||
* **popup:** ignore WinClosed from other popup ([a501202](https://github.com/MunifTanjim/nui.nvim/commit/a5012020a48f5740bf30bb3468ca532cb7627997))
|
||||
* **popup:** manual doautocmd BufWinEnter ([0807a9c](https://github.com/MunifTanjim/nui.nvim/commit/0807a9ca3274d5f89d02802aa00dac9fc2649864))
|
||||
* **popup:** position relative to buffer position ([f369333](https://github.com/MunifTanjim/nui.nvim/commit/f36933397a0689a96106d9aa74db320286f5ffda))
|
||||
* **popup:** properly apply winhighlight ([4396e44](https://github.com/MunifTanjim/nui.nvim/commit/4396e4427dc7ec80c58575096fdcad46d336a7ac))
|
||||
* **popup:** remove mutation in border:get() ([1179f2e](https://github.com/MunifTanjim/nui.nvim/commit/1179f2e3f5245ab585e1154478f45bab1cf41867))
|
||||
* **popup:** respect 'enter' option on subsequent mounts ([602e4d8](https://github.com/MunifTanjim/nui.nvim/commit/602e4d885fda5da9f015345fecb8c745ffedbf52))
|
||||
* **popup:** set noautocmd for opening border window ([4715f60](https://github.com/MunifTanjim/nui.nvim/commit/4715f6092443f0b8fb9a3bcb0cfd03202bb03477))
|
||||
* **popup:** set win_config.win explicitly if applicable ([d50d84a](https://github.com/MunifTanjim/nui.nvim/commit/d50d84a340a3088b8560bf74fa3d6f6ad0556134))
|
||||
* **popup:** store winid for parent window ([03131f8](https://github.com/MunifTanjim/nui.nvim/commit/03131f8aa1873d08009ca214727a2c699d73dfe6))
|
||||
* **popup:** take border size_delta into account when calculating position ([e67310b](https://github.com/MunifTanjim/nui.nvim/commit/e67310b23d21ebe8b12d9dbadb3dfa562dda5057))
|
||||
* **popup:** track mounted state ([c1db7f8](https://github.com/MunifTanjim/nui.nvim/commit/c1db7f8377dfce4f724f35eec74868a15443dccf))
|
||||
* **popup:** update position handling ([d87b561](https://github.com/MunifTanjim/nui.nvim/commit/d87b56193991e102516689f394101d1367bf8ef5))
|
||||
* **popup:** update state and buffer handling for hide/show ([02e9262](https://github.com/MunifTanjim/nui.nvim/commit/02e9262d2d1820c2ed573f3a09848affc6526704))
|
||||
* **popup:** use copy of border styles table item ([abd1a4a](https://github.com/MunifTanjim/nui.nvim/commit/abd1a4a23d0bbc0d0d4bc8faaa240330b44a4d5c))
|
||||
* **popup:** use popup winhighlight for border ([cf67636](https://github.com/MunifTanjim/nui.nvim/commit/cf676363181aae149271226531abca341e583997))
|
||||
* **split:** check bufnr is valid before clearing namespace ([e9889bb](https://github.com/MunifTanjim/nui.nvim/commit/e9889bbd9919544697d497537acacd9c67d0de99))
|
||||
* **split:** do buf_storage.cleanup after buffer wipeout ([5bf5d62](https://github.com/MunifTanjim/nui.nvim/commit/5bf5d62531b473b01cae65ea389d4f788fb2341f))
|
||||
* **split:** do not update position when unchanged ([6d86148](https://github.com/MunifTanjim/nui.nvim/commit/6d86148ad43554f51caa82e99d63a57be1654182))
|
||||
* **split:** set size after open window ([c45ad68](https://github.com/MunifTanjim/nui.nvim/commit/c45ad685cb156174761f912c7697fdb6fd5a3b50))
|
||||
* **split:** skip manual buf/win removal when pending quit ([cc76e6f](https://github.com/MunifTanjim/nui.nvim/commit/cc76e6ff13629b18d3dedfadd4f52e35ff085700))
|
||||
* **split:** tweak container size relative to editor ([120fe69](https://github.com/MunifTanjim/nui.nvim/commit/120fe69bc4d96a13f89c2450ceb27fcd921b77ae))
|
||||
* **split:** use self.winid for WinClosed ([747c20d](https://github.com/MunifTanjim/nui.nvim/commit/747c20d10a42f22a0125ee2e7397aee4c2422ffe))
|
||||
* support nui.text for internal alignment util ([915fabe](https://github.com/MunifTanjim/nui.nvim/commit/915fabe334639c384ed5c66005046b96d4e8d30a))
|
||||
* **text:** fix :highlight extmark.end_col ([3775746](https://github.com/MunifTanjim/nui.nvim/commit/3775746f8324db1ff5068eaf10f321db5e566611))
|
||||
* **tree:** fix calculation for method :render ([70f2dad](https://github.com/MunifTanjim/nui.nvim/commit/70f2dadb73b5aa15727ec8f7a620818997505be5))
|
||||
* **tree:** pass ns_id correctly to nui.line in :render method ([70fc6b6](https://github.com/MunifTanjim/nui.nvim/commit/70fc6b66c651538a78d393cf9fc830c81e919ca8))
|
||||
* **tree:** remove children recursively in method remove_node ([4939282](https://github.com/MunifTanjim/nui.nvim/commit/4939282919885e1c83aff68ecb35b3cadf6015a9))
|
||||
* **tree:** remove id from .nodes.root_ids on tree:remove_node ([792caa3](https://github.com/MunifTanjim/nui.nvim/commit/792caa3c1dc3efb22385b3b363c32567ba9cb059))
|
||||
* **tree:** set default buf_options.bufhidden=hide ([7c7bdf4](https://github.com/MunifTanjim/nui.nvim/commit/7c7bdf40219bc274a849fa241daa47872c809fd8))
|
||||
* **tree:** set default buf_options.undolevels=0 ([42552b3](https://github.com/MunifTanjim/nui.nvim/commit/42552b3797c3452c5c94e0c84a04fbda9591b9d1))
|
||||
* vim.schedule QuitPre event callback ([d147222](https://github.com/MunifTanjim/nui.nvim/commit/d147222a1300901656f3ebd5b95f91732785a329))
|
||||
* **window:** cleanup properly ([dbc8185](https://github.com/MunifTanjim/nui.nvim/commit/dbc81850faeeb70c303cdf3c50e0a4b6bbf154bd))
|
||||
* **window:** set zindex properly ([2d427f7](https://github.com/MunifTanjim/nui.nvim/commit/2d427f7f9bac670523de2906776117192f243d8c))
|
||||
* **window:** use 0-indexed position ([fbf96df](https://github.com/MunifTanjim/nui.nvim/commit/fbf96df95e437687206b9213924372c13c328b7a))
|
||||
|
||||
|
||||
### Continuous Integration
|
||||
|
||||
* introduce automated release ([70560c4](https://github.com/MunifTanjim/nui.nvim/commit/70560c4e7b36ff974e7136d08aa4022e79a002f4))
|
21
.config/nvim/pack/tree/start/nui.nvim/LICENSE
Normal file
21
.config/nvim/pack/tree/start/nui.nvim/LICENSE
Normal file
|
@ -0,0 +1,21 @@
|
|||
MIT License
|
||||
|
||||
Copyright (c) 2021 Munif Tanjim
|
||||
|
||||
Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||
of this software and associated documentation files (the "Software"), to deal
|
||||
in the Software without restriction, including without limitation the rights
|
||||
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
||||
copies of the Software, and to permit persons to whom the Software is
|
||||
furnished to do so, subject to the following conditions:
|
||||
|
||||
The above copyright notice and this permission notice shall be included in all
|
||||
copies or substantial portions of the Software.
|
||||
|
||||
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
||||
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
||||
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
||||
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
||||
SOFTWARE.
|
353
.config/nvim/pack/tree/start/nui.nvim/README.md
Normal file
353
.config/nvim/pack/tree/start/nui.nvim/README.md
Normal file
|
@ -0,0 +1,353 @@
|
|||

|
||||
[](https://codecov.io/gh/MunifTanjim/nui.nvim)
|
||||
[](https://luarocks.org/modules/MunifTanjim/nui.nvim)
|
||||

|
||||
|
||||
# nui.nvim
|
||||
|
||||
UI Component Library for Neovim.
|
||||
|
||||
## Requirements
|
||||
|
||||
- [Neovim 0.5.0](https://github.com/neovim/neovim/releases/tag/v0.5.0)
|
||||
|
||||
## Installation
|
||||
|
||||
Install the plugins with your preferred plugin manager. For example, with [`vim-plug`](https://github.com/junegunn/vim-plug):
|
||||
|
||||
```vim
|
||||
Plug 'MunifTanjim/nui.nvim'
|
||||
```
|
||||
|
||||
## Blocks
|
||||
|
||||
### [NuiText](lua/nui/text)
|
||||
|
||||
Quickly add highlighted text on the buffer.
|
||||
|
||||
**[Check Detailed Documentation for `nui.text`](lua/nui/text)**
|
||||
|
||||
**[Check Wiki Page for `nui.text`](https://github.com/MunifTanjim/nui.nvim/wiki/nui.text)**
|
||||
|
||||
### [NuiLine](lua/nui/line)
|
||||
|
||||
Quickly add line containing highlighted text chunks on the buffer.
|
||||
|
||||
**[Check Detailed Documentation for `nui.line`](lua/nui/line)**
|
||||
|
||||
**[Check Wiki Page for `nui.line`](https://github.com/MunifTanjim/nui.nvim/wiki/nui.line)**
|
||||
|
||||
### [NuiTable](lua/nui/table)
|
||||
|
||||
Quickly render table-like structured content on the buffer.
|
||||
|
||||
**[Check Detailed Documentation for `nui.table`](lua/nui/table)**
|
||||
|
||||
**[Check Wiki Page for `nui.table`](https://github.com/MunifTanjim/nui.nvim/wiki/nui.table)**
|
||||
|
||||
### [NuiTree](lua/nui/tree)
|
||||
|
||||
Quickly render tree-like structured content on the buffer.
|
||||
|
||||
**[Check Detailed Documentation for `nui.tree`](lua/nui/tree)**
|
||||
|
||||
**[Check Wiki Page for `nui.tree`](https://github.com/MunifTanjim/nui.nvim/wiki/nui.tree)**
|
||||
|
||||
## Components
|
||||
|
||||
### [Layout](lua/nui/layout)
|
||||
|
||||

|
||||
|
||||
```lua
|
||||
local Popup = require("nui.popup")
|
||||
local Layout = require("nui.layout")
|
||||
|
||||
local popup_one, popup_two = Popup({
|
||||
enter = true,
|
||||
border = "single",
|
||||
}), Popup({
|
||||
border = "double",
|
||||
})
|
||||
|
||||
local layout = Layout(
|
||||
{
|
||||
position = "50%",
|
||||
size = {
|
||||
width = 80,
|
||||
height = "60%",
|
||||
},
|
||||
},
|
||||
Layout.Box({
|
||||
Layout.Box(popup_one, { size = "40%" }),
|
||||
Layout.Box(popup_two, { size = "60%" }),
|
||||
}, { dir = "row" })
|
||||
)
|
||||
|
||||
local current_dir = "row"
|
||||
|
||||
popup_one:map("n", "r", function()
|
||||
if current_dir == "col" then
|
||||
layout:update(Layout.Box({
|
||||
Layout.Box(popup_one, { size = "40%" }),
|
||||
Layout.Box(popup_two, { size = "60%" }),
|
||||
}, { dir = "row" }))
|
||||
|
||||
current_dir = "row"
|
||||
else
|
||||
layout:update(Layout.Box({
|
||||
Layout.Box(popup_two, { size = "60%" }),
|
||||
Layout.Box(popup_one, { size = "40%" }),
|
||||
}, { dir = "col" }))
|
||||
|
||||
current_dir = "col"
|
||||
end
|
||||
end, {})
|
||||
|
||||
layout:mount()
|
||||
```
|
||||
|
||||
**[Check Detailed Documentation for `nui.layout`](lua/nui/layout)**
|
||||
|
||||
**[Check Wiki Page for `nui.layout`](https://github.com/MunifTanjim/nui.nvim/wiki/nui.layout)**
|
||||
|
||||
### [Popup](lua/nui/popup)
|
||||
|
||||

|
||||
|
||||
```lua
|
||||
local Popup = require("nui.popup")
|
||||
local event = require("nui.utils.autocmd").event
|
||||
|
||||
local popup = Popup({
|
||||
enter = true,
|
||||
focusable = true,
|
||||
border = {
|
||||
style = "rounded",
|
||||
},
|
||||
position = "50%",
|
||||
size = {
|
||||
width = "80%",
|
||||
height = "60%",
|
||||
},
|
||||
})
|
||||
|
||||
-- mount/open the component
|
||||
popup:mount()
|
||||
|
||||
-- unmount component when cursor leaves buffer
|
||||
popup:on(event.BufLeave, function()
|
||||
popup:unmount()
|
||||
end)
|
||||
|
||||
-- set content
|
||||
vim.api.nvim_buf_set_lines(popup.bufnr, 0, 1, false, { "Hello World" })
|
||||
```
|
||||
|
||||
**[Check Detailed Documentation for `nui.popup`](lua/nui/popup)**
|
||||
|
||||
**[Check Wiki Page for `nui.popup`](https://github.com/MunifTanjim/nui.nvim/wiki/nui.popup)**
|
||||
|
||||
### [Input](lua/nui/input)
|
||||
|
||||

|
||||
|
||||
```lua
|
||||
local Input = require("nui.input")
|
||||
local event = require("nui.utils.autocmd").event
|
||||
|
||||
local input = Input({
|
||||
position = "50%",
|
||||
size = {
|
||||
width = 20,
|
||||
},
|
||||
border = {
|
||||
style = "single",
|
||||
text = {
|
||||
top = "[Howdy?]",
|
||||
top_align = "center",
|
||||
},
|
||||
},
|
||||
win_options = {
|
||||
winhighlight = "Normal:Normal,FloatBorder:Normal",
|
||||
},
|
||||
}, {
|
||||
prompt = "> ",
|
||||
default_value = "Hello",
|
||||
on_close = function()
|
||||
print("Input Closed!")
|
||||
end,
|
||||
on_submit = function(value)
|
||||
print("Input Submitted: " .. value)
|
||||
end,
|
||||
})
|
||||
|
||||
-- mount/open the component
|
||||
input:mount()
|
||||
|
||||
-- unmount component when cursor leaves buffer
|
||||
input:on(event.BufLeave, function()
|
||||
input:unmount()
|
||||
end)
|
||||
```
|
||||
|
||||
**[Check Detailed Documentation for `nui.input`](lua/nui/input)**
|
||||
|
||||
**[Check Wiki Page for `nui.input`](https://github.com/MunifTanjim/nui.nvim/wiki/nui.input)**
|
||||
|
||||
### [Menu](lua/nui/menu)
|
||||
|
||||

|
||||
|
||||
```lua
|
||||
local Menu = require("nui.menu")
|
||||
local event = require("nui.utils.autocmd").event
|
||||
|
||||
local menu = Menu({
|
||||
position = "50%",
|
||||
size = {
|
||||
width = 25,
|
||||
height = 5,
|
||||
},
|
||||
border = {
|
||||
style = "single",
|
||||
text = {
|
||||
top = "[Choose-an-Element]",
|
||||
top_align = "center",
|
||||
},
|
||||
},
|
||||
win_options = {
|
||||
winhighlight = "Normal:Normal,FloatBorder:Normal",
|
||||
},
|
||||
}, {
|
||||
lines = {
|
||||
Menu.item("Hydrogen (H)"),
|
||||
Menu.item("Carbon (C)"),
|
||||
Menu.item("Nitrogen (N)"),
|
||||
Menu.separator("Noble-Gases", {
|
||||
char = "-",
|
||||
text_align = "right",
|
||||
}),
|
||||
Menu.item("Helium (He)"),
|
||||
Menu.item("Neon (Ne)"),
|
||||
Menu.item("Argon (Ar)"),
|
||||
},
|
||||
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("Menu Closed!")
|
||||
end,
|
||||
on_submit = function(item)
|
||||
print("Menu Submitted: ", item.text)
|
||||
end,
|
||||
})
|
||||
|
||||
-- mount the component
|
||||
menu:mount()
|
||||
```
|
||||
|
||||
**[Check Detailed Documentation for `nui.menu`](lua/nui/menu)**
|
||||
|
||||
**[Check Wiki Page for `nui.menu`](https://github.com/MunifTanjim/nui.nvim/wiki/nui.menu)**
|
||||
|
||||
### [Split](lua/nui/split)
|
||||
|
||||

|
||||
|
||||
```lua
|
||||
local Split = require("nui.split")
|
||||
local event = require("nui.utils.autocmd").event
|
||||
|
||||
local split = Split({
|
||||
relative = "editor",
|
||||
position = "bottom",
|
||||
size = "20%",
|
||||
})
|
||||
|
||||
-- mount/open the component
|
||||
split:mount()
|
||||
|
||||
-- unmount component when cursor leaves buffer
|
||||
split:on(event.BufLeave, function()
|
||||
split:unmount()
|
||||
end)
|
||||
```
|
||||
|
||||
**[Check Detailed Documentation for `nui.split`](lua/nui/split)**
|
||||
|
||||
**[Check Wiki Page for `nui.split`](https://github.com/MunifTanjim/nui.nvim/wiki/nui.split)**
|
||||
|
||||
## Extendibility
|
||||
|
||||
Each of the [blocks](#blocks) and [components](#components) can be extended to add new
|
||||
methods or change their behaviors.
|
||||
|
||||
```lua
|
||||
local Timer = Popup:extend("Timer")
|
||||
|
||||
function Timer:init(popup_options)
|
||||
local options = vim.tbl_deep_extend("force", popup_options or {}, {
|
||||
border = "double",
|
||||
focusable = false,
|
||||
position = { row = 0, col = "100%" },
|
||||
size = { width = 10, height = 1 },
|
||||
win_options = {
|
||||
winhighlight = "Normal:Normal,FloatBorder:SpecialChar",
|
||||
},
|
||||
})
|
||||
|
||||
Timer.super.init(self, options)
|
||||
end
|
||||
|
||||
function Timer:countdown(time, step, format)
|
||||
local function draw_content(text)
|
||||
local gap_width = 10 - vim.api.nvim_strwidth(text)
|
||||
vim.api.nvim_buf_set_lines(self.bufnr, 0, -1, false, {
|
||||
string.format(
|
||||
"%s%s%s",
|
||||
string.rep(" ", math.floor(gap_width / 2)),
|
||||
text,
|
||||
string.rep(" ", math.ceil(gap_width / 2))
|
||||
),
|
||||
})
|
||||
end
|
||||
|
||||
self:mount()
|
||||
|
||||
local remaining_time = time
|
||||
|
||||
draw_content(format(remaining_time))
|
||||
|
||||
vim.fn.timer_start(step, function()
|
||||
remaining_time = remaining_time - step
|
||||
|
||||
draw_content(format(remaining_time))
|
||||
|
||||
if remaining_time <= 0 then
|
||||
self:unmount()
|
||||
end
|
||||
end, { ["repeat"] = math.ceil(remaining_time / step) })
|
||||
end
|
||||
|
||||
local timer = Timer()
|
||||
|
||||
timer:countdown(10000, 1000, function(time)
|
||||
return tostring(time / 1000) .. "s"
|
||||
end)
|
||||
```
|
||||
|
||||
#### `nui.object`
|
||||
|
||||
A small object library is bundled with `nui.nvim`. It is, more or less, a clone of the
|
||||
[`kikito/middleclass`](https://github.com/kikito/middleclass) library.
|
||||
|
||||
[Check Wiki Page for `nui.object`](https://github.com/MunifTanjim/nui.nvim/wiki/nui.object)
|
||||
|
||||
## License
|
||||
|
||||
Licensed under the MIT License. Check the [LICENSE](./LICENSE) file for details.
|
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
|
|
@ -0,0 +1,27 @@
|
|||
rockspec_format = "3.0"
|
||||
package = "nui.nvim"
|
||||
version = "dev-1"
|
||||
source = {
|
||||
url = "git+https://github.com/MunifTanjim/nui.nvim.git",
|
||||
tag = nil,
|
||||
}
|
||||
description = {
|
||||
summary = "UI Component Library for Neovim.",
|
||||
detailed = [[
|
||||
UI Component Library for Neovim.
|
||||
]],
|
||||
license = "MIT",
|
||||
homepage = "https://github.com/MunifTanjim/nui.nvim",
|
||||
issues_url = "https://github.com/MunifTanjim/nui.nvim/issues",
|
||||
maintainer = "Munif Tanjim (https://muniftanjim.dev)",
|
||||
labels = {
|
||||
"neovim",
|
||||
},
|
||||
}
|
||||
build = {
|
||||
type = "builtin",
|
||||
}
|
||||
test = {
|
||||
type = "command",
|
||||
command = "scripts/test.sh",
|
||||
}
|
19
.config/nvim/pack/tree/start/nui.nvim/scripts/format.sh
Executable file
19
.config/nvim/pack/tree/start/nui.nvim/scripts/format.sh
Executable file
|
@ -0,0 +1,19 @@
|
|||
#!/usr/bin/env sh
|
||||
|
||||
r_v_major="0"
|
||||
r_v_minor="17"
|
||||
r_v_patch="1"
|
||||
|
||||
v="$(stylua --version | cut -d' ' -f2)"
|
||||
v_major="$(echo "${v}" | cut -d'.' -f1)"
|
||||
v_minor="$(echo "${v}" | cut -d'.' -f2)"
|
||||
v_patch="$(echo "${v}" | cut -d'.' -f3)"
|
||||
|
||||
v_error_message="required stylua ~v${r_v_major}.${r_v_minor}.${r_v_patch}, found v${v_major}.${v_minor}.${v_patch}"
|
||||
|
||||
if test ${v_major} -ne ${r_v_major} || test ${v_minor} -ne ${r_v_minor} || test ${v_patch} -lt ${r_v_patch}; then
|
||||
echo ${v_error_message} >&2
|
||||
exit 1
|
||||
fi
|
||||
|
||||
stylua --color always ${1} lua/nui/ tests/
|
3
.config/nvim/pack/tree/start/nui.nvim/scripts/lint.sh
Executable file
3
.config/nvim/pack/tree/start/nui.nvim/scripts/lint.sh
Executable file
|
@ -0,0 +1,3 @@
|
|||
#!/usr/bin/env sh
|
||||
|
||||
luacheck $@ .
|
|
@ -0,0 +1,35 @@
|
|||
diff --git a/lua/plenary/busted.lua b/lua/plenary/busted.lua
|
||||
index 1b15fce..8363084 100644
|
||||
--- a/lua/plenary/busted.lua
|
||||
+++ b/lua/plenary/busted.lua
|
||||
@@ -238,7 +238,7 @@ mod.run = function(file)
|
||||
-- If nothing runs (empty file without top level describe)
|
||||
if not results.pass then
|
||||
if is_headless then
|
||||
- return vim.cmd "0cq"
|
||||
+ os.exit(0)
|
||||
else
|
||||
return
|
||||
end
|
||||
@@ -259,7 +259,7 @@ mod.run = function(file)
|
||||
end
|
||||
else
|
||||
if is_headless then
|
||||
- return vim.cmd "0cq"
|
||||
+ os.exit(0)
|
||||
end
|
||||
end
|
||||
end)()
|
||||
diff --git a/lua/plenary/test_harness.lua b/lua/plenary/test_harness.lua
|
||||
index 394e28d..66cc6b4 100644
|
||||
--- a/lua/plenary/test_harness.lua
|
||||
+++ b/lua/plenary/test_harness.lua
|
||||
@@ -169,7 +169,7 @@ function harness.test_directory(directory, opts)
|
||||
return vim.cmd "1cq"
|
||||
end
|
||||
|
||||
- return vim.cmd "0cq"
|
||||
+ os.exit(0)
|
||||
end
|
||||
end
|
||||
|
97
.config/nvim/pack/tree/start/nui.nvim/scripts/test.sh
Executable file
97
.config/nvim/pack/tree/start/nui.nvim/scripts/test.sh
Executable file
|
@ -0,0 +1,97 @@
|
|||
#!/usr/bin/env bash
|
||||
|
||||
set -euo pipefail
|
||||
|
||||
declare -r plugins_dir="./.tests/site/pack/deps/start"
|
||||
declare -r module="nui"
|
||||
|
||||
declare test_scope="${module}"
|
||||
|
||||
while [[ $# -gt 0 ]]; do
|
||||
case "${1}" in
|
||||
--clean)
|
||||
shift
|
||||
echo "[test] cleaning up environment"
|
||||
rm -rf "${plugins_dir}"
|
||||
echo "[test] envionment cleaned"
|
||||
;;
|
||||
*)
|
||||
if [[ "${test_scope}" == "${module}" ]] && [[ "${1}" == "${module}/"* ]]; then
|
||||
test_scope="${1}"
|
||||
fi
|
||||
shift
|
||||
;;
|
||||
esac
|
||||
done
|
||||
|
||||
function setup_environment() {
|
||||
echo
|
||||
echo "[test] setting up environment"
|
||||
echo
|
||||
|
||||
if [[ ! -d "${plugins_dir}" ]]; then
|
||||
mkdir -p "${plugins_dir}"
|
||||
fi
|
||||
|
||||
if [[ ! -d "${plugins_dir}/plenary.nvim" ]]; then
|
||||
echo "[plugins] plenary.nvim: installing..."
|
||||
git clone https://github.com/nvim-lua/plenary.nvim "${plugins_dir}/plenary.nvim"
|
||||
# commit 9069d14a120cadb4f6825f76821533f2babcab92 broke luacov
|
||||
# issue: https://github.com/nvim-lua/plenary.nvim/issues/353
|
||||
local -r plenary_353_patch="$(pwd)/scripts/plenary-353.patch"
|
||||
git -C "${plugins_dir}/plenary.nvim" apply "${plenary_353_patch}"
|
||||
echo "[plugins] plenary.nvim: installed"
|
||||
echo
|
||||
fi
|
||||
|
||||
echo "[test] environment ready"
|
||||
echo
|
||||
}
|
||||
|
||||
function luacov_start() {
|
||||
luacov_dir="$(dirname "$(luarocks which luacov 2>/dev/null | head -1)")"
|
||||
if [[ "${luacov_dir}" == "." ]]; then
|
||||
luacov_dir=""
|
||||
fi
|
||||
|
||||
if test -n "${luacov_dir}"; then
|
||||
rm -f luacov.*.out
|
||||
export LUA_PATH=";;${luacov_dir}/?.lua"
|
||||
fi
|
||||
}
|
||||
|
||||
function luacov_end() {
|
||||
if test -n "${luacov_dir}"; then
|
||||
if test -f "luacov.stats.out"; then
|
||||
luacov
|
||||
|
||||
echo
|
||||
tail -n +$(($(grep -n "^Summary$" luacov.report.out | cut -d":" -f1) - 1)) luacov.report.out
|
||||
fi
|
||||
fi
|
||||
}
|
||||
|
||||
setup_environment
|
||||
|
||||
luacov_start
|
||||
|
||||
declare test_logs=""
|
||||
|
||||
if [[ -d "./tests/${test_scope}/" ]]; then
|
||||
test_logs=$(nvim --headless --noplugin -u tests/init.lua -c "lua require('plenary.test_harness').test_directory('./tests/${test_scope}/', { minimal_init = 'tests/init.lua', sequential = true })" || true)
|
||||
elif [[ -f "./tests/${test_scope}_spec.lua" ]]; then
|
||||
test_logs=$(nvim --headless --noplugin -u tests/init.lua -c "lua require('plenary.busted').run('./tests/${test_scope}_spec.lua')" || true)
|
||||
fi
|
||||
|
||||
echo "${test_logs}"
|
||||
|
||||
luacov_end
|
||||
|
||||
if echo "${test_logs}" | grep --quiet "stack traceback"; then
|
||||
{
|
||||
echo ""
|
||||
echo "FOUND STACK TRACEBACK IN TEST LOGS"
|
||||
echo ""
|
||||
} >&2
|
||||
exit 1
|
||||
fi
|
312
.config/nvim/pack/tree/start/nui.nvim/tests/helpers/init.lua
Normal file
312
.config/nvim/pack/tree/start/nui.nvim/tests/helpers/init.lua
Normal file
|
@ -0,0 +1,312 @@
|
|||
local function to_string(text)
|
||||
if type(text) == "string" then
|
||||
return text
|
||||
end
|
||||
|
||||
if type(text) == "table" then
|
||||
if text.content then
|
||||
return text:content()
|
||||
end
|
||||
|
||||
return text[1]
|
||||
end
|
||||
|
||||
error("unsupported text")
|
||||
end
|
||||
|
||||
local popup = {}
|
||||
|
||||
local mod = {}
|
||||
|
||||
mod.popup = popup
|
||||
|
||||
function mod.eq(...)
|
||||
return assert.are.same(...)
|
||||
end
|
||||
|
||||
function mod.approx(...)
|
||||
return assert.are.near(...)
|
||||
end
|
||||
|
||||
function mod.neq(...)
|
||||
return assert["not"].are.same(...)
|
||||
end
|
||||
|
||||
---@param fn fun(): nil
|
||||
---@param error string
|
||||
---@param is_plain boolean
|
||||
function mod.errors(fn, error, is_plain)
|
||||
assert.matches_error(fn, error, 1, is_plain)
|
||||
end
|
||||
|
||||
---@param keys string
|
||||
---@param mode string
|
||||
function mod.feedkeys(keys, mode)
|
||||
vim.api.nvim_feedkeys(vim.api.nvim_replace_termcodes(keys, true, false, true), mode or "", true)
|
||||
end
|
||||
|
||||
---@param tbl table
|
||||
---@param keys string[]
|
||||
function mod.tbl_pick(tbl, keys)
|
||||
if not keys or #keys == 0 then
|
||||
return tbl
|
||||
end
|
||||
|
||||
local new_tbl = {}
|
||||
for _, key in ipairs(keys) do
|
||||
new_tbl[key] = tbl[key]
|
||||
end
|
||||
return new_tbl
|
||||
end
|
||||
|
||||
---@param tbl table
|
||||
---@param keys string[]
|
||||
function mod.tbl_omit(tbl, keys)
|
||||
if not keys or #keys == 0 then
|
||||
return tbl
|
||||
end
|
||||
|
||||
local new_tbl = vim.deepcopy(tbl)
|
||||
for _, key in ipairs(keys) do
|
||||
rawset(new_tbl, key, nil)
|
||||
end
|
||||
return new_tbl
|
||||
end
|
||||
|
||||
---@param bufnr number
|
||||
---@param ns_id integer
|
||||
---@param linenr integer (1-indexed)
|
||||
---@param byte_start? integer (0-indexed)
|
||||
---@param byte_end? integer (0-indexed, inclusive)
|
||||
function mod.get_line_extmarks(bufnr, ns_id, linenr, byte_start, byte_end)
|
||||
return vim.api.nvim_buf_get_extmarks(
|
||||
bufnr,
|
||||
ns_id,
|
||||
{ linenr - 1, byte_start or 0 },
|
||||
{ linenr - 1, byte_end and byte_end + 1 or -1 },
|
||||
{ details = true }
|
||||
)
|
||||
end
|
||||
|
||||
---@param bufnr number
|
||||
---@param ns_id integer
|
||||
---@param linenr integer (1-indexed)
|
||||
---@param text string
|
||||
---@return table[]
|
||||
---@return { byte_start: integer, byte_end: integer } info (byte range: 0-indexed, inclusive)
|
||||
function mod.get_text_extmarks(bufnr, ns_id, linenr, text)
|
||||
local line = vim.api.nvim_buf_get_lines(bufnr, linenr - 1, linenr, false)[1]
|
||||
|
||||
local byte_start = string.find(line, text) -- 1-indexed
|
||||
byte_start = byte_start - 1 -- 0-indexed
|
||||
local byte_end = byte_start + #text - 1 -- inclusive
|
||||
|
||||
local extmarks = vim.api.nvim_buf_get_extmarks(
|
||||
bufnr,
|
||||
ns_id,
|
||||
{ linenr - 1, byte_start },
|
||||
{ linenr - 1, byte_end },
|
||||
{ details = true }
|
||||
)
|
||||
|
||||
return extmarks, { byte_start = byte_start, byte_end = byte_end }
|
||||
end
|
||||
|
||||
---@param bufnr number
|
||||
---@param lines string[]
|
||||
---@param linenr_start? integer (1-indexed)
|
||||
---@param linenr_end? integer (1-indexed, inclusive)
|
||||
function mod.assert_buf_lines(bufnr, lines, linenr_start, linenr_end)
|
||||
mod.eq(vim.api.nvim_buf_get_lines(bufnr, linenr_start and linenr_start - 1 or 0, linenr_end or -1, false), lines)
|
||||
end
|
||||
|
||||
---@param bufnr number
|
||||
---@param options table
|
||||
function mod.assert_buf_options(bufnr, options)
|
||||
for name, value in pairs(options) do
|
||||
mod.eq(vim.api.nvim_buf_get_option(bufnr, name), value)
|
||||
end
|
||||
end
|
||||
|
||||
---@param winid number
|
||||
---@param options table
|
||||
function mod.assert_win_options(winid, options)
|
||||
for name, value in pairs(options) do
|
||||
mod.eq(vim.api.nvim_win_get_option(winid, name), value)
|
||||
end
|
||||
end
|
||||
|
||||
---@param extmark table
|
||||
---@param linenr number (1-indexed)
|
||||
---@param text string
|
||||
---@param hl_group string
|
||||
function mod.assert_extmark(extmark, linenr, text, hl_group)
|
||||
mod.eq(extmark[2], linenr - 1)
|
||||
|
||||
if text then
|
||||
local start_col = extmark[3]
|
||||
mod.eq(extmark[4].end_col - start_col, #text)
|
||||
end
|
||||
|
||||
mod.eq(mod.tbl_pick(extmark[4], { "end_row", "hl_group" }), {
|
||||
end_row = linenr - 1,
|
||||
hl_group = hl_group,
|
||||
})
|
||||
end
|
||||
|
||||
---@param bufnr number
|
||||
---@param ns_id integer
|
||||
---@param linenr integer (1-indexed)
|
||||
---@param text string
|
||||
---@param hl_group string
|
||||
function mod.assert_highlight(bufnr, ns_id, linenr, text, hl_group)
|
||||
local extmarks, info = mod.get_text_extmarks(bufnr, ns_id, linenr, text)
|
||||
|
||||
mod.eq(#extmarks, 1)
|
||||
mod.eq(extmarks[1][3], info.byte_start)
|
||||
mod.assert_extmark(extmarks[1], linenr, text, hl_group)
|
||||
end
|
||||
|
||||
---@param feature_name string
|
||||
---@param desc string
|
||||
---@param func fun(is_available: boolean):nil
|
||||
function mod.describe_flipping_feature(feature_name, desc, func)
|
||||
local initial_value = require("nui.utils")._.feature[feature_name]
|
||||
|
||||
describe(string.format("(w/ %s) %s", feature_name, desc), function()
|
||||
require("nui.utils")._.feature[feature_name] = true
|
||||
func(true)
|
||||
require("nui.utils")._.feature[feature_name] = initial_value
|
||||
end)
|
||||
|
||||
describe(string.format("(w/o %s) %s", feature_name, desc), function()
|
||||
require("nui.utils")._.feature[feature_name] = false
|
||||
func(false)
|
||||
require("nui.utils")._.feature[feature_name] = initial_value
|
||||
end)
|
||||
end
|
||||
|
||||
function popup.create_border_style_list()
|
||||
return { "╭", "─", "╮", "│", "╯", "─", "╰", "│" }
|
||||
end
|
||||
|
||||
function popup.create_border_style_map()
|
||||
return {
|
||||
top_left = "╭",
|
||||
top = "─",
|
||||
top_right = "╮",
|
||||
left = "│",
|
||||
right = "│",
|
||||
bottom_left = "╰",
|
||||
bottom = "─",
|
||||
bottom_right = "╯",
|
||||
}
|
||||
end
|
||||
|
||||
function popup.create_border_style_map_with_tuple(hl_group)
|
||||
local style = popup.create_border_style_map()
|
||||
for k, v in pairs(style) do
|
||||
style[k] = { v, hl_group .. "_" .. k }
|
||||
end
|
||||
return style
|
||||
end
|
||||
|
||||
function popup.create_border_style_map_with_nui_text(hl_group)
|
||||
local Text = require("nui.text")
|
||||
|
||||
local style = popup.create_border_style_map()
|
||||
for k, v in pairs(style) do
|
||||
style[k] = Text(v, hl_group .. "_" .. k)
|
||||
end
|
||||
|
||||
return style
|
||||
end
|
||||
|
||||
function popup.assert_border_lines(options, border_bufnr)
|
||||
local size = { width = options.size.width, height = options.size.height }
|
||||
-- `vim.tbl_islist` will be removed in the future
|
||||
local islist = vim.islist or vim.tbl_islist
|
||||
|
||||
local style = vim.deepcopy(options.border.style)
|
||||
if islist(style) then
|
||||
style = {
|
||||
top_left = style[1],
|
||||
top = style[2],
|
||||
top_right = style[3],
|
||||
left = style[8],
|
||||
right = style[4],
|
||||
bottom_left = style[7],
|
||||
bottom = style[6],
|
||||
bottom_right = style[5],
|
||||
}
|
||||
end
|
||||
|
||||
local expected_lines = {}
|
||||
table.insert(
|
||||
expected_lines,
|
||||
string.format(
|
||||
"%s%s%s",
|
||||
to_string(style.top_left),
|
||||
string.rep(to_string(style.top), size.width),
|
||||
to_string(style.top_right)
|
||||
)
|
||||
)
|
||||
for _ = 1, size.height do
|
||||
table.insert(
|
||||
expected_lines,
|
||||
string.format("%s%s%s", to_string(style.left), string.rep(" ", size.width), to_string(style.right))
|
||||
)
|
||||
end
|
||||
table.insert(
|
||||
expected_lines,
|
||||
string.format(
|
||||
"%s%s%s",
|
||||
to_string(style.bottom_left),
|
||||
string.rep(to_string(style.bottom), size.width),
|
||||
to_string(style.bottom_right)
|
||||
)
|
||||
)
|
||||
|
||||
mod.assert_buf_lines(border_bufnr, expected_lines)
|
||||
end
|
||||
|
||||
function popup.assert_border_highlight(options, border_bufnr, hl_group, no_hl_group_suffix)
|
||||
local size = { width = options.size.width, height = options.size.height }
|
||||
|
||||
for linenr = 1, size.height + 2 do
|
||||
local is_top_line = linenr == 1
|
||||
local is_bottom_line = linenr == size.height + 2
|
||||
|
||||
local extmarks = mod.get_line_extmarks(border_bufnr, options.ns_id, linenr)
|
||||
|
||||
mod.eq(#extmarks, (is_top_line or is_bottom_line) and 4 or 2)
|
||||
|
||||
local function with_suffix(hl_group_name, suffix)
|
||||
if no_hl_group_suffix then
|
||||
return hl_group_name
|
||||
end
|
||||
return hl_group_name .. suffix
|
||||
end
|
||||
|
||||
mod.assert_extmark(
|
||||
extmarks[1],
|
||||
linenr,
|
||||
nil,
|
||||
with_suffix(hl_group, (is_top_line and "_top_left" or is_bottom_line and "_bottom_left" or "_left"))
|
||||
)
|
||||
|
||||
if is_top_line or is_bottom_line then
|
||||
mod.assert_extmark(extmarks[2], linenr, nil, with_suffix(hl_group, (is_top_line and "_top" or "_bottom")))
|
||||
mod.assert_extmark(extmarks[3], linenr, nil, with_suffix(hl_group, (is_top_line and "_top" or "_bottom")))
|
||||
end
|
||||
|
||||
mod.assert_extmark(
|
||||
extmarks[#extmarks],
|
||||
linenr,
|
||||
nil,
|
||||
with_suffix(hl_group, (is_top_line and "_top_right" or is_bottom_line and "_bottom_right" or "_right"))
|
||||
)
|
||||
end
|
||||
end
|
||||
|
||||
return mod
|
24
.config/nvim/pack/tree/start/nui.nvim/tests/init.lua
Normal file
24
.config/nvim/pack/tree/start/nui.nvim/tests/init.lua
Normal file
|
@ -0,0 +1,24 @@
|
|||
-- mimic startup option `--clean`
|
||||
local function clean_startup()
|
||||
for _, path in ipairs(vim.split(vim.o.runtimepath, ",")) do
|
||||
if
|
||||
string.find(path, vim.fn.expand("~/.config/nvim"))
|
||||
or string.find(path, vim.fn.expand("~/.local/share/nvim/site"))
|
||||
then
|
||||
vim.opt.packpath:remove(path)
|
||||
vim.opt.runtimepath:remove(path)
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
clean_startup()
|
||||
|
||||
local root_dir = vim.fn.fnamemodify(vim.trim(vim.fn.system("git rev-parse --show-toplevel")), ":p"):gsub("/$", "")
|
||||
|
||||
package.path = string.format("%s;%s/?.lua;%s/?/init.lua", package.path, root_dir, root_dir)
|
||||
|
||||
vim.opt.packpath:prepend(root_dir .. "/.tests/site")
|
||||
|
||||
vim.cmd([[
|
||||
packadd plenary.nvim
|
||||
]])
|
|
@ -0,0 +1,285 @@
|
|||
pcall(require, "luacov")
|
||||
|
||||
local Input = require("nui.input")
|
||||
local Text = require("nui.text")
|
||||
local h = require("tests.helpers")
|
||||
|
||||
local eq, feedkeys = h.eq, h.feedkeys
|
||||
|
||||
-- Input's functionalities are not testable using headless nvim.
|
||||
-- Not sure what to do about it.
|
||||
|
||||
describe("nui.input", function()
|
||||
local parent_winid, parent_bufnr
|
||||
local popup_options
|
||||
local input
|
||||
|
||||
before_each(function()
|
||||
parent_winid = vim.api.nvim_get_current_win()
|
||||
parent_bufnr = vim.api.nvim_get_current_buf()
|
||||
|
||||
popup_options = {
|
||||
relative = "win",
|
||||
position = "50%",
|
||||
size = 20,
|
||||
}
|
||||
end)
|
||||
|
||||
after_each(function()
|
||||
if input then
|
||||
input:unmount()
|
||||
input = nil
|
||||
end
|
||||
end)
|
||||
|
||||
pending("o.prompt", function()
|
||||
it("supports NuiText", function()
|
||||
local prompt_text = "> "
|
||||
local hl_group = "NuiInputTest"
|
||||
|
||||
input = Input(popup_options, {
|
||||
prompt = Text(prompt_text, hl_group),
|
||||
})
|
||||
|
||||
input:mount()
|
||||
vim.wait(100, function() end)
|
||||
|
||||
h.assert_buf_lines(input.bufnr, {
|
||||
prompt_text,
|
||||
})
|
||||
|
||||
h.assert_highlight(input.bufnr, input.ns_id, 1, prompt_text, hl_group)
|
||||
end)
|
||||
end)
|
||||
|
||||
describe("o.on_change", function()
|
||||
it("works", function()
|
||||
local done = false
|
||||
local values = {}
|
||||
|
||||
input = Input(popup_options, {
|
||||
on_change = function(value)
|
||||
values[#values + 1] = value
|
||||
end,
|
||||
on_close = function()
|
||||
done = true
|
||||
end,
|
||||
})
|
||||
|
||||
input:mount()
|
||||
vim.wait(100, function() end)
|
||||
|
||||
feedkeys("aa", "x") -- append a
|
||||
feedkeys("ab", "x") -- append b
|
||||
feedkeys("ac", "x") -- append c
|
||||
|
||||
vim.fn.wait(100, function()
|
||||
return done
|
||||
end)
|
||||
|
||||
eq(values, { "a", "ab", "abc" })
|
||||
end)
|
||||
end)
|
||||
|
||||
describe("o.on_close", function()
|
||||
it("is called on <C-c>", function()
|
||||
local done = false
|
||||
|
||||
input = Input(popup_options, {
|
||||
on_close = function()
|
||||
done = true
|
||||
end,
|
||||
})
|
||||
|
||||
input:mount()
|
||||
vim.wait(100, function() end)
|
||||
|
||||
feedkeys("i<C-c>", "x")
|
||||
|
||||
vim.fn.wait(2000, function()
|
||||
return done
|
||||
end)
|
||||
|
||||
eq(done, true)
|
||||
end)
|
||||
|
||||
it("is called on unmount", function()
|
||||
local done = false
|
||||
|
||||
input = Input(popup_options, {
|
||||
on_close = function()
|
||||
done = true
|
||||
end,
|
||||
})
|
||||
|
||||
input:mount()
|
||||
vim.wait(100, function() end)
|
||||
|
||||
input:unmount()
|
||||
|
||||
vim.fn.wait(200, function()
|
||||
return done
|
||||
end)
|
||||
|
||||
eq(done, true)
|
||||
end)
|
||||
end)
|
||||
|
||||
describe("cursor_position_patch", function()
|
||||
local initial_cursor
|
||||
|
||||
local function setup()
|
||||
vim.api.nvim_buf_set_lines(parent_bufnr, 0, -1, false, {
|
||||
"1 nui.nvim",
|
||||
"2 nui.nvim",
|
||||
"3 nui.nvim",
|
||||
})
|
||||
initial_cursor = { 2, 4 }
|
||||
vim.api.nvim_win_set_cursor(parent_winid, initial_cursor)
|
||||
end
|
||||
|
||||
it("works after submitting from insert mode", function()
|
||||
setup()
|
||||
|
||||
local done = false
|
||||
input = Input(popup_options, {
|
||||
on_submit = function()
|
||||
done = true
|
||||
end,
|
||||
})
|
||||
|
||||
input:mount()
|
||||
vim.wait(100, function() end)
|
||||
|
||||
feedkeys("<cr>", "x")
|
||||
|
||||
vim.fn.wait(1000, function()
|
||||
return done
|
||||
end)
|
||||
|
||||
eq(done, true)
|
||||
eq(vim.api.nvim_win_get_cursor(parent_winid), initial_cursor)
|
||||
end)
|
||||
|
||||
it("works after submitting from normal mode", function()
|
||||
setup()
|
||||
|
||||
local done = false
|
||||
input = Input(popup_options, {
|
||||
on_submit = function()
|
||||
done = true
|
||||
end,
|
||||
})
|
||||
|
||||
input:mount()
|
||||
vim.wait(100, function() end)
|
||||
|
||||
feedkeys("<esc><cr>", "x")
|
||||
|
||||
vim.fn.wait(1000, function()
|
||||
return done
|
||||
end)
|
||||
|
||||
eq(done, true)
|
||||
eq(vim.api.nvim_win_get_cursor(parent_winid), initial_cursor)
|
||||
end)
|
||||
|
||||
it("works after closing from insert mode", function()
|
||||
setup()
|
||||
|
||||
local done = false
|
||||
input = Input(popup_options, {
|
||||
on_close = function()
|
||||
done = true
|
||||
end,
|
||||
})
|
||||
|
||||
input:mount()
|
||||
vim.wait(100, function() end)
|
||||
|
||||
input:map("i", "<esc>", function()
|
||||
input:unmount()
|
||||
end, { nowait = true, noremap = true })
|
||||
|
||||
feedkeys("i<esc>", "x")
|
||||
|
||||
vim.fn.wait(1000, function()
|
||||
return done
|
||||
end)
|
||||
|
||||
eq(done, true)
|
||||
eq(vim.api.nvim_win_get_cursor(parent_winid), initial_cursor)
|
||||
end)
|
||||
|
||||
it("works after closing from normal mode", function()
|
||||
setup()
|
||||
|
||||
local done = false
|
||||
input = Input(popup_options, {
|
||||
on_close = function()
|
||||
done = true
|
||||
end,
|
||||
})
|
||||
|
||||
input:mount()
|
||||
vim.wait(100, function() end)
|
||||
|
||||
input:map("n", "<esc>", function()
|
||||
input:unmount()
|
||||
end, { nowait = true, noremap = true })
|
||||
|
||||
feedkeys("<esc>", "x")
|
||||
|
||||
vim.fn.wait(1000, function()
|
||||
return done
|
||||
end)
|
||||
|
||||
eq(done, true)
|
||||
eq(vim.api.nvim_win_get_cursor(parent_winid), initial_cursor)
|
||||
end)
|
||||
end)
|
||||
|
||||
describe("method :mount", function()
|
||||
it("is idempotent", function()
|
||||
input = Input(popup_options, {})
|
||||
|
||||
input:mount()
|
||||
vim.wait(100, function() end)
|
||||
|
||||
local bufnr, winid = input.bufnr, input.winid
|
||||
|
||||
eq(type(bufnr), "number")
|
||||
eq(type(winid), "number")
|
||||
|
||||
input:mount()
|
||||
|
||||
eq(bufnr, input.bufnr)
|
||||
eq(winid, input.winid)
|
||||
end)
|
||||
end)
|
||||
|
||||
describe("method :unmount", function()
|
||||
it("is idempotent", function()
|
||||
local done = 0
|
||||
|
||||
input = Input(popup_options, {
|
||||
on_close = function()
|
||||
done = done + 1
|
||||
end,
|
||||
})
|
||||
|
||||
input:mount()
|
||||
vim.wait(100, function() end)
|
||||
|
||||
input:unmount()
|
||||
input:unmount()
|
||||
input:unmount()
|
||||
|
||||
vim.fn.wait(200, function()
|
||||
return done > 1
|
||||
end)
|
||||
|
||||
eq(done, 1)
|
||||
end)
|
||||
end)
|
||||
end)
|
2026
.config/nvim/pack/tree/start/nui.nvim/tests/nui/layout/init_spec.lua
Normal file
2026
.config/nvim/pack/tree/start/nui.nvim/tests/nui/layout/init_spec.lua
Normal file
File diff suppressed because it is too large
Load diff
|
@ -0,0 +1,144 @@
|
|||
pcall(require, "luacov")
|
||||
|
||||
local utils = require("nui.layout.utils")
|
||||
local h = require("tests.helpers")
|
||||
|
||||
local eq = h.eq
|
||||
|
||||
describe("nui.layout", function()
|
||||
describe("utils", function()
|
||||
describe("parse_relative", function()
|
||||
local fallback_winid = 17
|
||||
|
||||
it("works for type=buf", function()
|
||||
local relative = {
|
||||
type = "buf",
|
||||
position = { row = 2, col = 4 },
|
||||
winid = 42,
|
||||
}
|
||||
|
||||
local result = utils.parse_relative(relative, fallback_winid)
|
||||
|
||||
eq(result, {
|
||||
relative = "win",
|
||||
win = relative.winid,
|
||||
bufpos = {
|
||||
relative.position.row,
|
||||
relative.position.col,
|
||||
},
|
||||
})
|
||||
end)
|
||||
|
||||
it("works for type=cursor", function()
|
||||
local relative = {
|
||||
type = "cursor",
|
||||
winid = 42,
|
||||
}
|
||||
|
||||
local result = utils.parse_relative(relative, fallback_winid)
|
||||
|
||||
eq(result, {
|
||||
relative = relative.type,
|
||||
win = relative.winid,
|
||||
})
|
||||
end)
|
||||
|
||||
it("works for type=editor", function()
|
||||
local relative = {
|
||||
type = "editor",
|
||||
winid = 42,
|
||||
}
|
||||
|
||||
local result = utils.parse_relative(relative, fallback_winid)
|
||||
|
||||
eq(result, {
|
||||
relative = relative.type,
|
||||
win = relative.winid,
|
||||
})
|
||||
end)
|
||||
|
||||
it("works for type=win", function()
|
||||
local relative = {
|
||||
type = "win",
|
||||
winid = 42,
|
||||
}
|
||||
|
||||
local result = utils.parse_relative(relative, fallback_winid)
|
||||
|
||||
eq(result, {
|
||||
relative = relative.type,
|
||||
win = relative.winid,
|
||||
})
|
||||
end)
|
||||
|
||||
it("uses fallback_winid if relative.winid is nil", function()
|
||||
local relative = {
|
||||
type = "win",
|
||||
}
|
||||
|
||||
local result = utils.parse_relative(relative, fallback_winid)
|
||||
|
||||
eq(result, {
|
||||
relative = relative.type,
|
||||
win = fallback_winid,
|
||||
})
|
||||
end)
|
||||
end)
|
||||
|
||||
describe("get_container_info", function()
|
||||
it("works for relative=editor", function()
|
||||
local result = utils.get_container_info({
|
||||
relative = "editor",
|
||||
})
|
||||
|
||||
eq(result, {
|
||||
relative = "editor",
|
||||
size = {
|
||||
width = vim.o.columns,
|
||||
height = vim.o.lines,
|
||||
},
|
||||
type = "editor",
|
||||
})
|
||||
end)
|
||||
|
||||
it("works for relative=cursor", function()
|
||||
local winid = vim.api.nvim_get_current_win()
|
||||
|
||||
local result = utils.get_container_info({
|
||||
relative = "cursor",
|
||||
win = 0,
|
||||
})
|
||||
|
||||
eq(result, {
|
||||
relative = "cursor",
|
||||
size = {
|
||||
width = vim.api.nvim_win_get_width(winid),
|
||||
height = vim.api.nvim_win_get_height(winid),
|
||||
},
|
||||
type = "window",
|
||||
winid = winid,
|
||||
})
|
||||
end)
|
||||
|
||||
it("works for relative=win w/ bufpos", function()
|
||||
local winid = vim.api.nvim_get_current_win()
|
||||
|
||||
local result = utils.get_container_info({
|
||||
relative = "win",
|
||||
win = winid,
|
||||
bufpos = { 2, 4 },
|
||||
})
|
||||
|
||||
eq(result, {
|
||||
relative = "buf",
|
||||
size = {
|
||||
width = vim.api.nvim_win_get_width(winid),
|
||||
height = vim.api.nvim_win_get_height(winid),
|
||||
},
|
||||
type = "window",
|
||||
winid = winid,
|
||||
})
|
||||
end)
|
||||
end)
|
||||
end)
|
||||
end)
|
|
@ -0,0 +1,164 @@
|
|||
pcall(require, "luacov")
|
||||
|
||||
local Line = require("nui.line")
|
||||
local Text = require("nui.text")
|
||||
local h = require("tests.helpers")
|
||||
|
||||
local eq = h.eq
|
||||
|
||||
describe("nui.line", function()
|
||||
it("can accept initial nui.text objects", function()
|
||||
local t1, t2 = Text("One"), Text("Two")
|
||||
local line = Line({ t1, t2 })
|
||||
|
||||
eq(#line._texts, 2)
|
||||
end)
|
||||
|
||||
describe("method :append", function()
|
||||
it("returns nui.text for string parameter", function()
|
||||
local line = Line()
|
||||
local text = line:append("One")
|
||||
|
||||
eq(type(text.content), "function")
|
||||
end)
|
||||
|
||||
it("returns nui.text for nui.text parameter", function()
|
||||
local line = Line()
|
||||
local text = Text("One")
|
||||
local ret_text = line:append(text)
|
||||
|
||||
eq(text == ret_text, true)
|
||||
eq(type(ret_text.content), "function")
|
||||
end)
|
||||
|
||||
it("returns nui.line for nui.line parameter", function()
|
||||
local line = Line()
|
||||
|
||||
local content_line = Line({ Text("One"), Text("Two") })
|
||||
|
||||
local ret_content_line = line:append(content_line)
|
||||
|
||||
eq(content_line == ret_content_line, true)
|
||||
eq(type(ret_content_line.append), "function")
|
||||
end)
|
||||
|
||||
it("stores and returns block with same reference", function()
|
||||
local line = Line()
|
||||
|
||||
local text_one = line:append("One")
|
||||
|
||||
eq(line._texts[1] == text_one, true)
|
||||
|
||||
local text_two = Text("Two")
|
||||
local ret_text_two = line:append(text_two)
|
||||
|
||||
eq(text_two == ret_text_two, true)
|
||||
eq(line._texts[2] == text_two, true)
|
||||
eq(line._texts[2] == ret_text_two, true)
|
||||
|
||||
local text_three = Text("Three")
|
||||
local text_four = Text("Four")
|
||||
local content_line = Line({ text_three, text_four })
|
||||
local ret_content_line = line:append(content_line)
|
||||
|
||||
eq(content_line == ret_content_line, true)
|
||||
eq(line._texts[3] == content_line._texts[1], true)
|
||||
eq(line._texts[4] == content_line._texts[2], true)
|
||||
end)
|
||||
end)
|
||||
|
||||
describe("method :content", function()
|
||||
it("returns whole text content", function()
|
||||
local line = Line()
|
||||
line:append("One")
|
||||
line:append("Two")
|
||||
|
||||
eq(line:content(), "OneTwo")
|
||||
end)
|
||||
end)
|
||||
|
||||
describe("method :width", function()
|
||||
it("returns whole text width", function()
|
||||
local line = Line()
|
||||
line:append("One")
|
||||
line:append("Two")
|
||||
|
||||
eq(line:width(), 6)
|
||||
end)
|
||||
end)
|
||||
|
||||
describe("method", function()
|
||||
local winid, bufnr
|
||||
|
||||
before_each(function()
|
||||
winid = vim.api.nvim_get_current_win()
|
||||
bufnr = vim.api.nvim_create_buf(false, true)
|
||||
|
||||
vim.api.nvim_win_set_buf(winid, bufnr)
|
||||
end)
|
||||
|
||||
after_each(function()
|
||||
vim.api.nvim_buf_delete(bufnr, { force = true })
|
||||
end)
|
||||
|
||||
describe(":highlight", function()
|
||||
local hl_group_one, hl_group_two, ns, ns_id
|
||||
local linenr
|
||||
local t1, t2, t3, t4
|
||||
local line
|
||||
|
||||
before_each(function()
|
||||
hl_group_one = "NuiTextTestOne"
|
||||
hl_group_two = "NuiTextTestTwo"
|
||||
ns = "NuiTest"
|
||||
ns_id = vim.api.nvim_create_namespace(ns)
|
||||
|
||||
linenr = 1
|
||||
|
||||
t1 = Text("One")
|
||||
t2 = Text("Two", hl_group_one)
|
||||
t3 = Text("Three", hl_group_two)
|
||||
t4 = Text("Four")
|
||||
|
||||
line = Line({ t1, t2, t3, t4 })
|
||||
end)
|
||||
|
||||
it("is applied with :render", function()
|
||||
line:render(bufnr, ns_id, linenr)
|
||||
|
||||
h.assert_highlight(bufnr, ns_id, linenr, t2:content(), hl_group_one)
|
||||
h.assert_highlight(bufnr, ns_id, linenr, t3:content(), hl_group_two)
|
||||
end)
|
||||
|
||||
it("can highlight existing buffer line", function()
|
||||
vim.api.nvim_buf_set_lines(
|
||||
bufnr,
|
||||
linenr - 1,
|
||||
-1,
|
||||
false,
|
||||
{ t1:content() .. t2:content() .. t3:content() .. t4:content() }
|
||||
)
|
||||
|
||||
line:highlight(bufnr, ns_id, linenr)
|
||||
|
||||
h.assert_highlight(bufnr, ns_id, linenr, t2:content(), hl_group_one)
|
||||
h.assert_highlight(bufnr, ns_id, linenr, t3:content(), hl_group_two)
|
||||
end)
|
||||
end)
|
||||
|
||||
describe(":render", function()
|
||||
it("works", function()
|
||||
local linenr = 1
|
||||
|
||||
local line = Line()
|
||||
line:append("4")
|
||||
line:append("2")
|
||||
line:render(bufnr, -1, linenr)
|
||||
|
||||
h.assert_buf_lines(bufnr, {
|
||||
"42",
|
||||
})
|
||||
end)
|
||||
end)
|
||||
end)
|
||||
end)
|
|
@ -0,0 +1,620 @@
|
|||
pcall(require, "luacov")
|
||||
|
||||
local Menu = require("nui.menu")
|
||||
local Layout = require("nui.layout")
|
||||
local Line = require("nui.line")
|
||||
local Text = require("nui.text")
|
||||
local h = require("tests.helpers")
|
||||
local spy = require("luassert.spy")
|
||||
|
||||
local eq, feedkeys = h.eq, h.feedkeys
|
||||
|
||||
describe("nui.menu", function()
|
||||
local callbacks
|
||||
local popup_options
|
||||
local menu
|
||||
|
||||
before_each(function()
|
||||
callbacks = {
|
||||
on_change = function() end,
|
||||
on_submit = function() end,
|
||||
}
|
||||
|
||||
popup_options = {
|
||||
relative = "win",
|
||||
position = "50%",
|
||||
}
|
||||
end)
|
||||
|
||||
after_each(function()
|
||||
if menu then
|
||||
menu:unmount()
|
||||
menu = nil
|
||||
end
|
||||
end)
|
||||
|
||||
describe("method :new", function()
|
||||
it("works with menu", function()
|
||||
menu = Menu:new(popup_options, {
|
||||
lines = {
|
||||
Menu.item("a"),
|
||||
},
|
||||
})
|
||||
|
||||
menu:mount()
|
||||
|
||||
h.assert_buf_lines(menu.bufnr, {
|
||||
"a",
|
||||
})
|
||||
end)
|
||||
end)
|
||||
|
||||
describe("o.keymap", function()
|
||||
it("supports multiple keys as table", function()
|
||||
local on_change = spy.on(callbacks, "on_change")
|
||||
|
||||
local lines = {
|
||||
Menu.item("Item 1", { id = 1 }),
|
||||
Menu.item("Item 2", { id = 2 }),
|
||||
Menu.item("Item 3", { id = 3 }),
|
||||
}
|
||||
|
||||
menu = Menu(popup_options, {
|
||||
keymap = {
|
||||
focus_next = { "j", "s" },
|
||||
focus_prev = { "k", "w" },
|
||||
},
|
||||
lines = lines,
|
||||
on_change = on_change,
|
||||
})
|
||||
|
||||
menu:mount()
|
||||
|
||||
feedkeys("j", "x")
|
||||
assert.spy(on_change).called_with(lines[2], menu)
|
||||
on_change:clear()
|
||||
|
||||
feedkeys("s", "x")
|
||||
assert.spy(on_change).called_with(lines[3], menu)
|
||||
on_change:clear()
|
||||
|
||||
feedkeys("w", "x")
|
||||
assert.spy(on_change).called_with(lines[2], menu)
|
||||
on_change:clear()
|
||||
|
||||
feedkeys("k", "x")
|
||||
assert.spy(on_change).called_with(lines[1], menu)
|
||||
on_change:clear()
|
||||
end)
|
||||
|
||||
it("supports single key as string", function()
|
||||
local on_change = spy.on(callbacks, "on_change")
|
||||
|
||||
local lines = {
|
||||
Menu.item("Item 1", { id = 1 }),
|
||||
Menu.item("Item 2", { id = 2 }),
|
||||
Menu.item("Item 3", { id = 3 }),
|
||||
}
|
||||
|
||||
menu = Menu(popup_options, {
|
||||
keymap = {
|
||||
focus_next = "s",
|
||||
focus_prev = "w",
|
||||
},
|
||||
lines = lines,
|
||||
on_change = on_change,
|
||||
})
|
||||
|
||||
menu:mount()
|
||||
|
||||
feedkeys("s", "x")
|
||||
assert.spy(on_change).called_with(lines[2], menu)
|
||||
on_change:clear()
|
||||
|
||||
feedkeys("w", "x")
|
||||
assert.spy(on_change).called_with(lines[1], menu)
|
||||
on_change:clear()
|
||||
end)
|
||||
end)
|
||||
|
||||
describe("size", function()
|
||||
it("respects o.min_width", function()
|
||||
local min_width = 3
|
||||
|
||||
local items = {
|
||||
Menu.item("A"),
|
||||
Menu.separator("*"),
|
||||
Menu.item("B"),
|
||||
}
|
||||
|
||||
menu = Menu(popup_options, {
|
||||
lines = items,
|
||||
min_width = min_width,
|
||||
})
|
||||
|
||||
menu:mount()
|
||||
|
||||
eq(vim.api.nvim_win_get_width(menu.winid), min_width)
|
||||
|
||||
h.assert_buf_lines(menu.bufnr, {
|
||||
"A",
|
||||
" * ",
|
||||
"B",
|
||||
})
|
||||
end)
|
||||
|
||||
it("respects o.max_width", function()
|
||||
local max_width = 6
|
||||
|
||||
local items = {
|
||||
Menu.item("Item 1"),
|
||||
Menu.separator("*"),
|
||||
Menu.item("Item Number Two"),
|
||||
}
|
||||
|
||||
menu = Menu(popup_options, {
|
||||
lines = items,
|
||||
max_width = max_width,
|
||||
})
|
||||
|
||||
menu:mount()
|
||||
|
||||
eq(vim.api.nvim_win_get_width(menu.winid), max_width)
|
||||
|
||||
h.assert_buf_lines(menu.bufnr, {
|
||||
"Item 1",
|
||||
" * ",
|
||||
"Item …",
|
||||
})
|
||||
end)
|
||||
|
||||
it("respects o.min_height", function()
|
||||
local min_height = 3
|
||||
|
||||
local items = {
|
||||
Menu.item("A"),
|
||||
Menu.separator("*"),
|
||||
Menu.item("B"),
|
||||
}
|
||||
|
||||
menu = Menu(popup_options, {
|
||||
lines = items,
|
||||
min_height = min_height,
|
||||
})
|
||||
|
||||
menu:mount()
|
||||
|
||||
eq(vim.api.nvim_win_get_height(menu.winid), min_height)
|
||||
end)
|
||||
|
||||
it("respects o.max_height", function()
|
||||
local max_height = 2
|
||||
|
||||
local items = {
|
||||
Menu.item("A"),
|
||||
Menu.separator("*"),
|
||||
Menu.item("B"),
|
||||
}
|
||||
|
||||
menu = Menu(popup_options, {
|
||||
lines = items,
|
||||
max_height = max_height,
|
||||
})
|
||||
|
||||
menu:mount()
|
||||
|
||||
eq(vim.api.nvim_win_get_height(menu.winid), max_height)
|
||||
end)
|
||||
end)
|
||||
|
||||
it("calls o.on_change item focus is changed", function()
|
||||
local on_change = spy.on(callbacks, "on_change")
|
||||
|
||||
local lines = {
|
||||
Menu.item("Item 1", { id = 1 }),
|
||||
Menu.item("Item 2", { id = 2 }),
|
||||
}
|
||||
|
||||
menu = Menu(popup_options, {
|
||||
lines = lines,
|
||||
on_change = on_change,
|
||||
})
|
||||
|
||||
menu:mount()
|
||||
|
||||
-- initial focus
|
||||
assert.spy(on_change).called_with(lines[1], menu)
|
||||
on_change:clear()
|
||||
|
||||
feedkeys("j", "x")
|
||||
assert.spy(on_change).called_with(lines[2], menu)
|
||||
on_change:clear()
|
||||
|
||||
feedkeys("j", "x")
|
||||
assert.spy(on_change).called_with(lines[1], menu)
|
||||
on_change:clear()
|
||||
|
||||
feedkeys("k", "x")
|
||||
assert.spy(on_change).called_with(lines[2], menu)
|
||||
on_change:clear()
|
||||
end)
|
||||
|
||||
it("calls o.on_submit when item is submitted", function()
|
||||
local on_submit = spy.on(callbacks, "on_submit")
|
||||
|
||||
local lines = {
|
||||
Menu.item("Item 1", { id = 1 }),
|
||||
Menu.item("Item 2", { id = 2 }),
|
||||
}
|
||||
|
||||
menu = Menu(popup_options, {
|
||||
lines = lines,
|
||||
on_submit = on_submit,
|
||||
})
|
||||
|
||||
menu:mount()
|
||||
|
||||
feedkeys("j", "x")
|
||||
feedkeys("<CR>", "x")
|
||||
|
||||
assert.spy(on_submit).called_with(lines[2])
|
||||
end)
|
||||
|
||||
it("calls o.on_close when menu is closed", function()
|
||||
local on_close = spy.on(callbacks, "on_close")
|
||||
|
||||
local lines = {
|
||||
Menu.item("Item 1", { id = 1 }),
|
||||
Menu.item("Item 2", { id = 2 }),
|
||||
}
|
||||
|
||||
menu = Menu(popup_options, {
|
||||
lines = lines,
|
||||
on_close = on_close,
|
||||
})
|
||||
|
||||
menu:mount()
|
||||
|
||||
feedkeys("<Esc>", "x")
|
||||
|
||||
assert.spy(on_close).called_with()
|
||||
end)
|
||||
|
||||
describe("item", function()
|
||||
it("is prepared using o.prepare_item if provided", function()
|
||||
local items = {
|
||||
Menu.item("A"),
|
||||
Menu.separator("*"),
|
||||
Menu.item("B"),
|
||||
}
|
||||
|
||||
local function prepare_item(item)
|
||||
return "-" .. item.text .. "-"
|
||||
end
|
||||
|
||||
menu = Menu(popup_options, {
|
||||
lines = items,
|
||||
prepare_item = prepare_item,
|
||||
})
|
||||
|
||||
menu:mount()
|
||||
|
||||
h.assert_buf_lines(menu.bufnr, vim.tbl_map(prepare_item, items))
|
||||
end)
|
||||
|
||||
it("is prepared when o.prepare_item is not provided", function()
|
||||
local items = {
|
||||
Menu.item("A"),
|
||||
Menu.separator("*"),
|
||||
Menu.item("B"),
|
||||
}
|
||||
|
||||
popup_options.border = "single"
|
||||
|
||||
menu = Menu(popup_options, {
|
||||
lines = items,
|
||||
})
|
||||
|
||||
menu:mount()
|
||||
|
||||
h.assert_buf_lines(menu.bufnr, {
|
||||
"A",
|
||||
"─*──",
|
||||
"B",
|
||||
})
|
||||
end)
|
||||
|
||||
it("is skipped respecting o.should_skip_item if provided", function()
|
||||
local on_change = spy.on(callbacks, "on_change")
|
||||
|
||||
local items = {
|
||||
Menu.item("-"),
|
||||
Menu.item("A", { id = 1 }),
|
||||
Menu.item("-"),
|
||||
Menu.item("B", { id = 2 }),
|
||||
Menu.item("-"),
|
||||
}
|
||||
|
||||
menu = Menu(popup_options, {
|
||||
lines = items,
|
||||
on_change = on_change,
|
||||
should_skip_item = function(item)
|
||||
return not item.id
|
||||
end,
|
||||
})
|
||||
|
||||
menu:mount()
|
||||
|
||||
assert.spy(on_change).called_with(items[2], menu)
|
||||
on_change:clear()
|
||||
|
||||
feedkeys("j", "x")
|
||||
assert.spy(on_change).called_with(items[4], menu)
|
||||
on_change:clear()
|
||||
|
||||
feedkeys("j", "x")
|
||||
assert.spy(on_change).called_with(items[2], menu)
|
||||
on_change:clear()
|
||||
end)
|
||||
|
||||
it("supports table with key .text", function()
|
||||
local text = "text"
|
||||
|
||||
local items = {
|
||||
Menu.item({ text = text }),
|
||||
}
|
||||
|
||||
menu = Menu(popup_options, {
|
||||
lines = items,
|
||||
})
|
||||
|
||||
menu:mount()
|
||||
|
||||
h.assert_buf_lines(menu.bufnr, {
|
||||
text,
|
||||
})
|
||||
end)
|
||||
|
||||
it("supports nui.text", function()
|
||||
local hl_group = "NuiMenuTest"
|
||||
local text = "text"
|
||||
local items = {
|
||||
Menu.item(Text(text, hl_group)),
|
||||
}
|
||||
|
||||
menu = Menu(popup_options, {
|
||||
lines = items,
|
||||
})
|
||||
|
||||
menu:mount()
|
||||
|
||||
h.assert_buf_lines(menu.bufnr, {
|
||||
text,
|
||||
})
|
||||
|
||||
h.assert_highlight(menu.bufnr, menu.ns_id, 1, text, hl_group)
|
||||
end)
|
||||
|
||||
it("supports nui.line", function()
|
||||
local hl_group = "NuiMenuTest"
|
||||
local text = "text"
|
||||
local items = {
|
||||
Menu.item(Line({ Text(text, hl_group) })),
|
||||
}
|
||||
|
||||
menu = Menu(popup_options, {
|
||||
lines = items,
|
||||
})
|
||||
|
||||
menu:mount()
|
||||
|
||||
h.assert_buf_lines(menu.bufnr, {
|
||||
text,
|
||||
})
|
||||
|
||||
h.assert_highlight(menu.bufnr, menu.ns_id, 1, text, hl_group)
|
||||
end)
|
||||
|
||||
it("content longer than max_width is truncated", function()
|
||||
local items = {
|
||||
Menu.item({ text = "Item 10 -" }),
|
||||
Menu.item(Text("Item 20 -")),
|
||||
Menu.item(Line({ Text("Item 30 -") })),
|
||||
Menu.item(Line({ Text("Item 40"), Text(" -") })),
|
||||
Menu.item(Line({ Text("Item 50 -"), Text(" -") })),
|
||||
}
|
||||
|
||||
menu = Menu(popup_options, {
|
||||
max_width = 7,
|
||||
lines = items,
|
||||
})
|
||||
|
||||
menu:mount()
|
||||
|
||||
h.assert_buf_lines(menu.bufnr, {
|
||||
"Item 1…",
|
||||
"Item 2…",
|
||||
"Item 3…",
|
||||
"Item 4…",
|
||||
"Item 5…",
|
||||
})
|
||||
end)
|
||||
end)
|
||||
|
||||
it("can truncate content longer than max_width w/ multi-byte chars", function()
|
||||
menu = Menu(popup_options, {
|
||||
lines = {
|
||||
Menu.item("中文长度测试"),
|
||||
Menu.item("Test中英文测试"),
|
||||
Menu.item("Long Long Group"),
|
||||
},
|
||||
max_width = 11,
|
||||
})
|
||||
|
||||
menu:mount()
|
||||
|
||||
h.assert_buf_lines(menu.bufnr, {
|
||||
"中文长度测…",
|
||||
"Test中英文…",
|
||||
"Long Long …",
|
||||
})
|
||||
end)
|
||||
|
||||
describe("separator", function()
|
||||
it("text supports string", function()
|
||||
menu = Menu(popup_options, {
|
||||
lines = {
|
||||
Menu.item("A"),
|
||||
Menu.separator("Group"),
|
||||
},
|
||||
min_width = 10,
|
||||
})
|
||||
|
||||
menu:mount()
|
||||
|
||||
h.assert_buf_lines(menu.bufnr, {
|
||||
"A",
|
||||
" Group ",
|
||||
})
|
||||
end)
|
||||
|
||||
it("content longer than max_width is truncated", function()
|
||||
menu = Menu(popup_options, {
|
||||
lines = {
|
||||
Menu.item("A"),
|
||||
Menu.separator("Long Long Group"),
|
||||
},
|
||||
max_width = 10,
|
||||
})
|
||||
|
||||
menu:mount()
|
||||
|
||||
h.assert_buf_lines(menu.bufnr, {
|
||||
"A",
|
||||
" Long Lo… ",
|
||||
})
|
||||
end)
|
||||
|
||||
it("text supports nui.text", function()
|
||||
local hl_group = "NuiMenuTest"
|
||||
local text = "Group"
|
||||
|
||||
menu = Menu(popup_options, {
|
||||
lines = {
|
||||
Menu.item("A"),
|
||||
Menu.separator(Text(text, hl_group)),
|
||||
},
|
||||
min_width = 10,
|
||||
})
|
||||
|
||||
menu:mount()
|
||||
|
||||
h.assert_buf_lines(menu.bufnr, {
|
||||
"A",
|
||||
" Group ",
|
||||
})
|
||||
|
||||
h.assert_highlight(menu.bufnr, menu.ns_id, 2, text, hl_group)
|
||||
end)
|
||||
|
||||
it("text supports nui.line", function()
|
||||
local hl_group = "NuiMenuTest"
|
||||
local text = "Group"
|
||||
|
||||
menu = Menu(popup_options, {
|
||||
lines = {
|
||||
Menu.item("A"),
|
||||
Menu.separator(Line({ Text(text, hl_group), Text(" nui.text") })),
|
||||
},
|
||||
min_width = 10,
|
||||
})
|
||||
|
||||
menu:mount()
|
||||
|
||||
h.assert_buf_lines(menu.bufnr, {
|
||||
"A",
|
||||
" Group nui.t… ",
|
||||
})
|
||||
|
||||
h.assert_highlight(menu.bufnr, menu.ns_id, 2, text, hl_group)
|
||||
end)
|
||||
|
||||
it("o.char supports string", function()
|
||||
menu = Menu(popup_options, {
|
||||
lines = {
|
||||
Menu.item("A"),
|
||||
Menu.separator("Group", {
|
||||
char = "*",
|
||||
text_align = "right",
|
||||
}),
|
||||
},
|
||||
min_width = 10,
|
||||
})
|
||||
|
||||
menu:mount()
|
||||
|
||||
h.assert_buf_lines(menu.bufnr, {
|
||||
"A",
|
||||
"****Group*",
|
||||
})
|
||||
end)
|
||||
|
||||
it("o.char supports nui.text", function()
|
||||
local hl_group = "NuiMenuTest"
|
||||
|
||||
menu = Menu(popup_options, {
|
||||
lines = {
|
||||
Menu.item("A"),
|
||||
Menu.separator("Group", {
|
||||
char = Text("*", hl_group),
|
||||
text_align = "center",
|
||||
}),
|
||||
},
|
||||
min_width = 10,
|
||||
})
|
||||
|
||||
menu:mount()
|
||||
|
||||
h.assert_buf_lines(menu.bufnr, {
|
||||
"A",
|
||||
"**Group***",
|
||||
})
|
||||
|
||||
local linenr = 2
|
||||
|
||||
local extmarks = h.get_line_extmarks(menu.bufnr, menu.ns_id, linenr)
|
||||
|
||||
eq(#extmarks, 4)
|
||||
h.assert_extmark(extmarks[1], linenr, "*", hl_group)
|
||||
h.assert_extmark(extmarks[2], linenr, "*", hl_group)
|
||||
h.assert_extmark(extmarks[3], linenr, "**", hl_group)
|
||||
h.assert_extmark(extmarks[4], linenr, "*", hl_group)
|
||||
end)
|
||||
end)
|
||||
|
||||
describe("w/ Layout", function()
|
||||
it("can be used", function()
|
||||
menu = Menu({}, {
|
||||
lines = {
|
||||
Menu.item("A"),
|
||||
},
|
||||
})
|
||||
|
||||
local layout = Layout(
|
||||
{
|
||||
position = "50%",
|
||||
size = "100%",
|
||||
},
|
||||
Layout.Box({
|
||||
Layout.Box(menu, { size = "100%" }),
|
||||
})
|
||||
)
|
||||
|
||||
layout:mount()
|
||||
|
||||
h.assert_buf_lines(menu.bufnr, {
|
||||
"A",
|
||||
})
|
||||
end)
|
||||
end)
|
||||
end)
|
|
@ -0,0 +1,413 @@
|
|||
pcall(require, "luacov")
|
||||
|
||||
local h = require("tests.helpers")
|
||||
local Object = require("nui.object")
|
||||
local spy = require("luassert.spy")
|
||||
|
||||
local function assert_class(Class, SuperClass, name)
|
||||
h.eq(type(Class), "table")
|
||||
|
||||
h.eq(Class.super, SuperClass)
|
||||
h.eq(Class.name, name)
|
||||
h.eq(tostring(Class), "class " .. name)
|
||||
|
||||
h.eq(type(Class.new), "function")
|
||||
h.eq(type(Class.extend), "function")
|
||||
|
||||
local is_callable = pcall(function()
|
||||
return Class()
|
||||
end)
|
||||
h.eq(is_callable, true)
|
||||
end
|
||||
|
||||
local function assert_instance(instance, Class)
|
||||
h.eq(instance.class, Class)
|
||||
h.eq(tostring(instance), "instance of class " .. Class.name)
|
||||
|
||||
h.eq(instance.name, nil)
|
||||
h.eq(instance.super, nil)
|
||||
h.eq(instance.static, nil)
|
||||
|
||||
h.eq(instance.new, nil)
|
||||
h.eq(instance.extend, nil)
|
||||
end
|
||||
|
||||
local function create_classes(...)
|
||||
local by_name = {}
|
||||
local classes = {}
|
||||
|
||||
for i, def in ipairs({ ... }) do
|
||||
if type(def) == "string" then
|
||||
local class = Object(def)
|
||||
assert_class(class, nil, def)
|
||||
by_name[def] = class
|
||||
classes[i] = class
|
||||
elseif type(def) == "table" then
|
||||
local super = type(def[2]) == "table" and def[2] or (by_name[def[2]] and by_name[def[2]] or nil)
|
||||
local class = super and super:extend(def[1]) or Object(def[1])
|
||||
assert_class(class, super, def[1])
|
||||
by_name[def[1]] = class
|
||||
classes[i] = class
|
||||
else
|
||||
error("invalid argument")
|
||||
end
|
||||
end
|
||||
|
||||
return unpack(classes)
|
||||
end
|
||||
|
||||
describe("nui.object", function()
|
||||
describe("class", function()
|
||||
it("can be created", function()
|
||||
local Class = Object("Class")
|
||||
assert_class(Class, nil, "Class")
|
||||
end)
|
||||
|
||||
describe("static", function()
|
||||
describe("method", function()
|
||||
describe(":new", function()
|
||||
it("is called when creating instance", function()
|
||||
local Class = Object("Class")
|
||||
|
||||
spy.on(Class.static, "new")
|
||||
Class()
|
||||
assert.spy(Class.static.new).called_with(Class)
|
||||
Class.static.new:revert()
|
||||
|
||||
spy.on(Class.static, "new")
|
||||
Class:new()
|
||||
assert.spy(Class.static.new).called_with(Class)
|
||||
Class.static.new:revert()
|
||||
end)
|
||||
|
||||
it("creates new instance", function()
|
||||
local Class = Object("Class")
|
||||
|
||||
local instance = Class:new()
|
||||
assert_instance(instance, Class)
|
||||
end)
|
||||
end)
|
||||
|
||||
describe(":extend", function()
|
||||
it("creates subclass", function()
|
||||
local Class = Object("Class")
|
||||
|
||||
local SubClass = Class:extend("SubClass")
|
||||
assert_class(SubClass, Class, "SubClass")
|
||||
end)
|
||||
end)
|
||||
|
||||
describe(":is_subclass_of", function()
|
||||
it("works", function()
|
||||
local A, B, C = create_classes("A", { "B", "A" }, { "C", "B" })
|
||||
|
||||
for _, class in ipairs({ A, B, C }) do
|
||||
h.eq(class.is_subclass_of, Object.is_subclass)
|
||||
end
|
||||
|
||||
h.eq(A:is_subclass_of(A), false)
|
||||
h.eq(A:is_subclass_of(B), false)
|
||||
h.eq(A:is_subclass_of(C), false)
|
||||
|
||||
h.eq(B:is_subclass_of(A), true)
|
||||
h.eq(B:is_subclass_of(B), false)
|
||||
h.eq(B:is_subclass_of(C), false)
|
||||
|
||||
h.eq(C:is_subclass_of(A), true)
|
||||
h.eq(C:is_subclass_of(B), true)
|
||||
h.eq(C:is_subclass_of(C), false)
|
||||
end)
|
||||
end)
|
||||
end)
|
||||
|
||||
local function define_static_say_level(A)
|
||||
A.static.level = 1
|
||||
function A.static.say_level(class)
|
||||
return "Level: " .. class.level
|
||||
end
|
||||
|
||||
h.eq(A.level, 1)
|
||||
h.eq(A:say_level(), "Level: 1")
|
||||
end
|
||||
|
||||
it("can be defined for class", function()
|
||||
local A = create_classes("A")
|
||||
define_static_say_level(A)
|
||||
end)
|
||||
|
||||
it("is inherited by subclass", function()
|
||||
local A, B = create_classes("A", { "B", "A" })
|
||||
|
||||
define_static_say_level(A)
|
||||
|
||||
h.eq(B.level, 1)
|
||||
h.eq(B:say_level(), "Level: 1")
|
||||
|
||||
local C, D = create_classes({ "C", A }, { "D", B })
|
||||
|
||||
h.eq(C.level, 1)
|
||||
h.eq(C:say_level(), "Level: 1")
|
||||
|
||||
h.eq(D.level, 1)
|
||||
h.eq(D:say_level(), "Level: 1")
|
||||
end)
|
||||
|
||||
it("can be redefined for subclass", function()
|
||||
local A = create_classes("A")
|
||||
define_static_say_level(A)
|
||||
|
||||
local B = create_classes({ "B", A })
|
||||
|
||||
B.static.level = 2
|
||||
h.eq(B:say_level(), "Level: 2")
|
||||
|
||||
function B.static.say_level(class)
|
||||
return "LEVEL: " .. class.level
|
||||
end
|
||||
h.eq(B:say_level(), "LEVEL: 2")
|
||||
|
||||
local C, D = create_classes({ "C", A }, { "D", B })
|
||||
|
||||
C.static.level = 2
|
||||
h.eq(C:say_level(), "Level: 2")
|
||||
|
||||
D.static.level = 3
|
||||
h.eq(D:say_level(), "LEVEL: 3")
|
||||
end)
|
||||
|
||||
it("for subclass does not affect super", function()
|
||||
local A = create_classes("A")
|
||||
define_static_say_level(A)
|
||||
|
||||
local B = create_classes({ "B", A })
|
||||
|
||||
B.static.level = 2
|
||||
function B.static.say_level(class)
|
||||
return "LEVEL: " .. class.level
|
||||
end
|
||||
|
||||
h.eq(A:say_level(), "Level: 1")
|
||||
|
||||
local C = create_classes({ "C", B })
|
||||
|
||||
function C.static.say_name(class)
|
||||
return class.name
|
||||
end
|
||||
|
||||
h.eq(C:say_name(), "C")
|
||||
|
||||
h.eq(type(C.say_name), "function")
|
||||
h.eq(type(B.say_name), "nil")
|
||||
h.eq(type(A.say_name), "nil")
|
||||
end)
|
||||
end)
|
||||
|
||||
describe("instance", function()
|
||||
it("can be created", function()
|
||||
local A = create_classes("A")
|
||||
|
||||
local a = A:new()
|
||||
assert_instance(a, A)
|
||||
end)
|
||||
|
||||
describe("method", function()
|
||||
describe(":is_instance_of", function()
|
||||
it("works", function()
|
||||
local A, B, C, D = create_classes("A", { "B", "A" }, { "C", "B" }, "D")
|
||||
|
||||
local a, b, c, d = A:new(), B:new(), C:new(), D:new()
|
||||
|
||||
for _, instance in ipairs({ a, b, c, d }) do
|
||||
h.eq(instance.is_instance_of, Object.is_instance)
|
||||
end
|
||||
|
||||
h.eq(a:is_instance_of(A), true)
|
||||
h.eq(a:is_instance_of(B), false)
|
||||
h.eq(a:is_instance_of(C), false)
|
||||
h.eq(a:is_instance_of(D), false)
|
||||
|
||||
h.eq(b:is_instance_of(A), true)
|
||||
h.eq(b:is_instance_of(B), true)
|
||||
h.eq(b:is_instance_of(C), false)
|
||||
h.eq(b:is_instance_of(D), false)
|
||||
|
||||
h.eq(c:is_instance_of(A), true)
|
||||
h.eq(c:is_instance_of(B), true)
|
||||
h.eq(c:is_instance_of(C), true)
|
||||
h.eq(c:is_instance_of(D), false)
|
||||
|
||||
h.eq(d:is_instance_of(A), false)
|
||||
h.eq(d:is_instance_of(B), false)
|
||||
h.eq(d:is_instance_of(C), false)
|
||||
h.eq(d:is_instance_of(D), true)
|
||||
end)
|
||||
end)
|
||||
|
||||
it("can be defined", function()
|
||||
local A = create_classes("A")
|
||||
|
||||
function A:before_instance_creation()
|
||||
return "before " .. self.class.name .. " instance"
|
||||
end
|
||||
|
||||
local a = A:new()
|
||||
|
||||
function A:after_instance_creation()
|
||||
return "after " .. self.class.name .. " instance"
|
||||
end
|
||||
|
||||
h.eq(a:before_instance_creation(), "before A instance")
|
||||
h.eq(a:after_instance_creation(), "after A instance")
|
||||
end)
|
||||
|
||||
it("can be inherited", function()
|
||||
local A, B = create_classes("A", { "B", "A" })
|
||||
|
||||
function A:say_class_name()
|
||||
return self.class.name
|
||||
end
|
||||
|
||||
local a = A:new()
|
||||
h.eq(a:say_class_name(), "A")
|
||||
|
||||
local b = B:new()
|
||||
h.eq(b:say_class_name(), "B")
|
||||
|
||||
local C = create_classes({ "C", B })
|
||||
|
||||
local c = C:new()
|
||||
h.eq(c:say_class_name(), "C")
|
||||
end)
|
||||
|
||||
it("can be redefined", function()
|
||||
local A, B = create_classes("A", { "B", "A" })
|
||||
|
||||
function A:say_class_name()
|
||||
return self.class.name
|
||||
end
|
||||
|
||||
local a = A:new()
|
||||
h.eq(a:say_class_name(), "A")
|
||||
|
||||
function B:say_class_name()
|
||||
return string.lower(self.class.name)
|
||||
end
|
||||
|
||||
local b = B:new()
|
||||
h.eq(b:say_class_name(), "b")
|
||||
|
||||
local C = create_classes({ "C", B })
|
||||
|
||||
local c = C:new()
|
||||
h.eq(c:say_class_name(), "c")
|
||||
|
||||
function C:say_class_name()
|
||||
return string.rep(self.class.name, 3)
|
||||
end
|
||||
|
||||
h.eq(c:say_class_name(), "CCC")
|
||||
|
||||
C.say_class_name = nil
|
||||
|
||||
h.eq(c:say_class_name(), "c")
|
||||
|
||||
B.say_class_name = nil
|
||||
|
||||
h.eq(c:say_class_name(), "C")
|
||||
end)
|
||||
end)
|
||||
|
||||
describe("metamethod", function()
|
||||
describe("__index", function()
|
||||
it("can be set to table", function()
|
||||
local A = create_classes("A")
|
||||
|
||||
function A:upper(str) -- luacheck: no unused args
|
||||
return string.upper(str)
|
||||
end
|
||||
|
||||
A.__index = {
|
||||
upper = function(_, str)
|
||||
return str
|
||||
end,
|
||||
lower = function(_, str)
|
||||
return string.lower(str)
|
||||
end,
|
||||
}
|
||||
|
||||
local a = A()
|
||||
|
||||
h.eq(a:upper("y"), "Y")
|
||||
|
||||
h.eq(a:lower("Y"), "y")
|
||||
|
||||
A.__index = nil
|
||||
|
||||
h.eq(type(a.lower), "nil")
|
||||
end)
|
||||
|
||||
it("can be set to function", function()
|
||||
local A = create_classes("A")
|
||||
|
||||
function A:upper(str) -- luacheck: no unused args
|
||||
return string.upper(str)
|
||||
end
|
||||
|
||||
local index = {
|
||||
upper = function(self, str) -- luacheck: no unused args
|
||||
return str
|
||||
end,
|
||||
lower = function(self, str) -- luacheck: no unused args
|
||||
return string.lower(str)
|
||||
end,
|
||||
}
|
||||
|
||||
A.__index = function(self, key) -- luacheck: no unused args
|
||||
return index[key]
|
||||
end
|
||||
|
||||
local a = A()
|
||||
|
||||
h.eq(a:upper("y"), "Y")
|
||||
|
||||
h.eq(a:lower("Y"), "y")
|
||||
|
||||
A.__index = nil
|
||||
|
||||
h.eq(type(a.lower), "nil")
|
||||
end)
|
||||
end)
|
||||
|
||||
describe("__tostring", function()
|
||||
it("can be redefined", function()
|
||||
local A, B = create_classes("A", { "B", "A" })
|
||||
|
||||
local a = A()
|
||||
|
||||
h.eq(tostring(a), "instance of class A")
|
||||
|
||||
function A:__tostring()
|
||||
return "class " .. self.class.name .. "'s child"
|
||||
end
|
||||
|
||||
h.eq(tostring(a), "class A's child")
|
||||
|
||||
local b = B()
|
||||
|
||||
h.eq(tostring(b), "class B's child")
|
||||
|
||||
function B:__tostring()
|
||||
return "child of " .. self.class.name
|
||||
end
|
||||
|
||||
h.eq(tostring(b), "child of B")
|
||||
|
||||
B.__tostring = nil
|
||||
|
||||
h.eq(tostring(b), "class B's child")
|
||||
end)
|
||||
end)
|
||||
end)
|
||||
end)
|
||||
end)
|
||||
end)
|
File diff suppressed because it is too large
Load diff
1230
.config/nvim/pack/tree/start/nui.nvim/tests/nui/popup/init_spec.lua
Normal file
1230
.config/nvim/pack/tree/start/nui.nvim/tests/nui/popup/init_spec.lua
Normal file
File diff suppressed because it is too large
Load diff
1006
.config/nvim/pack/tree/start/nui.nvim/tests/nui/split/init_spec.lua
Normal file
1006
.config/nvim/pack/tree/start/nui.nvim/tests/nui/split/init_spec.lua
Normal file
File diff suppressed because it is too large
Load diff
|
@ -0,0 +1,632 @@
|
|||
pcall(require, "luacov")
|
||||
|
||||
local Line = require("nui.line")
|
||||
local Table = require("nui.table")
|
||||
local Text = require("nui.text")
|
||||
local h = require("tests.helpers")
|
||||
|
||||
local eq = h.eq
|
||||
|
||||
describe("nui.table", function()
|
||||
---@type number, number
|
||||
local winid, bufnr
|
||||
|
||||
before_each(function()
|
||||
winid = vim.api.nvim_get_current_win()
|
||||
bufnr = vim.api.nvim_create_buf(false, true)
|
||||
|
||||
vim.api.nvim_win_set_buf(winid, bufnr)
|
||||
end)
|
||||
|
||||
after_each(function()
|
||||
vim.api.nvim_buf_delete(bufnr, { force = true })
|
||||
end)
|
||||
|
||||
describe("o.bufnr", function()
|
||||
it("throws if missing", function()
|
||||
local ok, err = pcall(Table, {})
|
||||
eq(ok, false)
|
||||
eq(type(string.match(err, "missing bufnr")), "string")
|
||||
end)
|
||||
|
||||
it("throws if invalid", function()
|
||||
local ok, err = pcall(Table, { bufnr = 999 })
|
||||
eq(ok, false)
|
||||
eq(type(string.match(err, "invalid bufnr ")), "string")
|
||||
end)
|
||||
|
||||
it("sets t.bufnr properly", function()
|
||||
local table = Table({ bufnr = bufnr })
|
||||
|
||||
eq(table.bufnr, bufnr)
|
||||
end)
|
||||
end)
|
||||
|
||||
describe("o.buf_options", function()
|
||||
it("sets default buf options emulating scratch-buffer", function()
|
||||
local table = Table({ bufnr = bufnr })
|
||||
|
||||
h.assert_buf_options(table.bufnr, {
|
||||
bufhidden = "hide",
|
||||
buflisted = false,
|
||||
buftype = "nofile",
|
||||
swapfile = false,
|
||||
})
|
||||
end)
|
||||
|
||||
it("locks buffer by default", function()
|
||||
local table = Table({ bufnr = bufnr })
|
||||
|
||||
h.assert_buf_options(table.bufnr, {
|
||||
modifiable = false,
|
||||
readonly = true,
|
||||
undolevels = 0,
|
||||
})
|
||||
end)
|
||||
|
||||
it("sets values", function()
|
||||
local table = Table({
|
||||
bufnr = bufnr,
|
||||
buf_options = {
|
||||
undolevels = -1,
|
||||
},
|
||||
})
|
||||
|
||||
h.assert_buf_options(table.bufnr, {
|
||||
undolevels = -1,
|
||||
})
|
||||
end)
|
||||
end)
|
||||
|
||||
describe("o.ns_id", function()
|
||||
it("sets t.ns_id if o.ns_id is string", function()
|
||||
local ns = "NuiTest"
|
||||
local table = Table({ bufnr = bufnr, ns_id = ns })
|
||||
|
||||
local namespaces = vim.api.nvim_get_namespaces()
|
||||
|
||||
eq(table.ns_id, namespaces[ns])
|
||||
end)
|
||||
|
||||
it("sets t.ns_id if o.ns_id is number", function()
|
||||
local ns = "NuiTest"
|
||||
local ns_id = vim.api.nvim_create_namespace(ns)
|
||||
local table = Table({ bufnr = bufnr, ns_id = ns_id })
|
||||
|
||||
eq(table.ns_id, ns_id)
|
||||
end)
|
||||
end)
|
||||
|
||||
describe("o.columns", function()
|
||||
describe(".id", function()
|
||||
it("fallbacks t o .accessor_key", function()
|
||||
local table = Table({
|
||||
bufnr = bufnr,
|
||||
columns = { { accessor_key = "ID" } },
|
||||
data = { { ID = 42 } },
|
||||
})
|
||||
|
||||
table:render()
|
||||
|
||||
vim.api.nvim_win_set_cursor(winid, { 2, 3 })
|
||||
|
||||
eq(table:get_cell().column.id, "ID")
|
||||
end)
|
||||
|
||||
for header_type, header in pairs({
|
||||
string = "ID",
|
||||
NuiText = Text("ID"),
|
||||
NuiLine = Line({ Text("I"), Text("D") }),
|
||||
}) do
|
||||
it(string.format("fallbacks to .header (%s)", header_type), function()
|
||||
local table = Table({
|
||||
bufnr = bufnr,
|
||||
columns = {
|
||||
{
|
||||
header = header,
|
||||
accessor_fn = function()
|
||||
return ""
|
||||
end,
|
||||
},
|
||||
},
|
||||
data = { {} },
|
||||
})
|
||||
|
||||
table:render()
|
||||
|
||||
vim.api.nvim_win_set_cursor(winid, { 4, 3 })
|
||||
|
||||
eq(table:get_cell().column.id, "ID")
|
||||
end)
|
||||
end
|
||||
|
||||
it("throws if missing", function()
|
||||
local ok, err = pcall(function()
|
||||
return Table({
|
||||
bufnr = bufnr,
|
||||
columns = { {} },
|
||||
})
|
||||
end)
|
||||
eq(ok, false)
|
||||
eq(type(string.match(err, "missing column id")), "string")
|
||||
end)
|
||||
end)
|
||||
end)
|
||||
|
||||
describe("method :render", function()
|
||||
local columns
|
||||
local data
|
||||
|
||||
before_each(function()
|
||||
columns = {
|
||||
{
|
||||
header = "First Name",
|
||||
accessor_key = "firstName",
|
||||
footer = "firstName",
|
||||
},
|
||||
{
|
||||
header = "Last Name",
|
||||
accessor_key = "lastName",
|
||||
footer = "lastName",
|
||||
},
|
||||
}
|
||||
|
||||
data = {
|
||||
{
|
||||
firstName = "tanner",
|
||||
lastName = "linsley",
|
||||
age = 24,
|
||||
visits = 100,
|
||||
status = "In Relationship",
|
||||
progress = 50,
|
||||
},
|
||||
{
|
||||
firstName = "tandy",
|
||||
lastName = "miller",
|
||||
age = 40,
|
||||
visits = 40,
|
||||
status = "Single",
|
||||
progress = 80,
|
||||
},
|
||||
{
|
||||
firstName = "joe",
|
||||
lastName = "dirte",
|
||||
age = 45,
|
||||
visits = 20,
|
||||
status = "Complicated",
|
||||
progress = 10,
|
||||
},
|
||||
}
|
||||
end)
|
||||
|
||||
it("can handle empty columns", function()
|
||||
local table = Table({
|
||||
bufnr = bufnr,
|
||||
data = data,
|
||||
})
|
||||
table:render()
|
||||
h.assert_buf_lines(table.bufnr, { "" })
|
||||
end)
|
||||
|
||||
it("can handle empty data", function()
|
||||
local table = Table({
|
||||
bufnr = bufnr,
|
||||
columns = {
|
||||
{
|
||||
accessor_key = "firstName",
|
||||
},
|
||||
},
|
||||
})
|
||||
table:render()
|
||||
h.assert_buf_lines(table.bufnr, { "" })
|
||||
end)
|
||||
|
||||
it("can handle empty columns and data", function()
|
||||
local table = Table({ bufnr = bufnr })
|
||||
table:render()
|
||||
h.assert_buf_lines(table.bufnr, { "" })
|
||||
end)
|
||||
|
||||
it("works w/ header w/ footer", function()
|
||||
local table = Table({
|
||||
bufnr = bufnr,
|
||||
columns = columns,
|
||||
data = data,
|
||||
})
|
||||
|
||||
table:render()
|
||||
|
||||
h.assert_buf_lines(table.bufnr, {
|
||||
"┌──────────┬─────────┐",
|
||||
"│First Name│Last Name│",
|
||||
"├──────────┼─────────┤",
|
||||
"│tanner │linsley │",
|
||||
"├──────────┼─────────┤",
|
||||
"│tandy │miller │",
|
||||
"├──────────┼─────────┤",
|
||||
"│joe │dirte │",
|
||||
"├──────────┼─────────┤",
|
||||
"│firstName │lastName │",
|
||||
"└──────────┴─────────┘",
|
||||
})
|
||||
end)
|
||||
|
||||
it("works w/ header w/o footer", function()
|
||||
for _, column in ipairs(columns) do
|
||||
column.align = "center"
|
||||
column.footer = nil
|
||||
end
|
||||
|
||||
local table = Table({
|
||||
bufnr = bufnr,
|
||||
columns = columns,
|
||||
data = data,
|
||||
})
|
||||
|
||||
table:render()
|
||||
|
||||
h.assert_buf_lines(table.bufnr, {
|
||||
"┌──────────┬─────────┐",
|
||||
"│First Name│Last Name│",
|
||||
"├──────────┼─────────┤",
|
||||
"│ tanner │ linsley │",
|
||||
"├──────────┼─────────┤",
|
||||
"│ tandy │ miller │",
|
||||
"├──────────┼─────────┤",
|
||||
"│ joe │ dirte │",
|
||||
"└──────────┴─────────┘",
|
||||
})
|
||||
end)
|
||||
|
||||
it("works w/o header w/ footer", function()
|
||||
for _, column in ipairs(columns) do
|
||||
column.header = nil
|
||||
end
|
||||
|
||||
local table = Table({
|
||||
bufnr = bufnr,
|
||||
columns = columns,
|
||||
data = data,
|
||||
})
|
||||
|
||||
table:render()
|
||||
|
||||
h.assert_buf_lines(table.bufnr, {
|
||||
"┌─────────┬────────┐",
|
||||
"│tanner │linsley │",
|
||||
"├─────────┼────────┤",
|
||||
"│tandy │miller │",
|
||||
"├─────────┼────────┤",
|
||||
"│joe │dirte │",
|
||||
"├─────────┼────────┤",
|
||||
"│firstName│lastName│",
|
||||
"└─────────┴────────┘",
|
||||
})
|
||||
end)
|
||||
|
||||
it("works w/o header w/o footer", function()
|
||||
for _, column in ipairs(columns) do
|
||||
column.header = nil
|
||||
column.footer = nil
|
||||
end
|
||||
|
||||
local table = Table({
|
||||
bufnr = bufnr,
|
||||
columns = columns,
|
||||
data = data,
|
||||
})
|
||||
|
||||
table:render()
|
||||
|
||||
h.assert_buf_lines(table.bufnr, {
|
||||
"┌──────┬───────┐",
|
||||
"│tanner│linsley│",
|
||||
"├──────┼───────┤",
|
||||
"│tandy │miller │",
|
||||
"├──────┼───────┤",
|
||||
"│joe │dirte │",
|
||||
"└──────┴───────┘",
|
||||
})
|
||||
end)
|
||||
|
||||
it("supports param linenr_start", function()
|
||||
vim.api.nvim_buf_set_lines(bufnr, 0, -1, false, {
|
||||
"START: NuiTest",
|
||||
"",
|
||||
"END: NuiTest",
|
||||
})
|
||||
|
||||
local table = Table({
|
||||
bufnr = bufnr,
|
||||
columns = columns,
|
||||
data = { data[1] },
|
||||
})
|
||||
|
||||
table:render(2)
|
||||
h.assert_buf_lines(table.bufnr, {
|
||||
"START: NuiTest",
|
||||
"┌──────────┬─────────┐",
|
||||
"│First Name│Last Name│",
|
||||
"├──────────┼─────────┤",
|
||||
"│tanner │linsley │",
|
||||
"├──────────┼─────────┤",
|
||||
"│firstName │lastName │",
|
||||
"└──────────┴─────────┘",
|
||||
"END: NuiTest",
|
||||
})
|
||||
|
||||
table:render(4)
|
||||
h.assert_buf_lines(table.bufnr, {
|
||||
"START: NuiTest",
|
||||
"",
|
||||
"",
|
||||
"┌──────────┬─────────┐",
|
||||
"│First Name│Last Name│",
|
||||
"├──────────┼─────────┤",
|
||||
"│tanner │linsley │",
|
||||
"├──────────┼─────────┤",
|
||||
"│firstName │lastName │",
|
||||
"└──────────┴─────────┘",
|
||||
"END: NuiTest",
|
||||
})
|
||||
|
||||
table:render(3)
|
||||
h.assert_buf_lines(table.bufnr, {
|
||||
"START: NuiTest",
|
||||
"",
|
||||
"┌──────────┬─────────┐",
|
||||
"│First Name│Last Name│",
|
||||
"├──────────┼─────────┤",
|
||||
"│tanner │linsley │",
|
||||
"├──────────┼─────────┤",
|
||||
"│firstName │lastName │",
|
||||
"└──────────┴─────────┘",
|
||||
"END: NuiTest",
|
||||
})
|
||||
end)
|
||||
|
||||
describe("grouped columns", function()
|
||||
local grouped_columns
|
||||
before_each(function()
|
||||
grouped_columns = {
|
||||
{
|
||||
header = "Name",
|
||||
footer = function(info)
|
||||
return info.column.id
|
||||
end,
|
||||
columns = {
|
||||
{
|
||||
accessor_key = "firstName",
|
||||
footer = "firstName",
|
||||
},
|
||||
{
|
||||
id = "lastName",
|
||||
header = "Last Name",
|
||||
accessor_key = "lastName",
|
||||
footer = function(info)
|
||||
return info.column.id
|
||||
end,
|
||||
},
|
||||
},
|
||||
},
|
||||
{
|
||||
header = "Info",
|
||||
footer = function(info)
|
||||
return info.column.id
|
||||
end,
|
||||
columns = {
|
||||
{
|
||||
header = "Age",
|
||||
accessor_key = "age",
|
||||
footer = "age",
|
||||
},
|
||||
{
|
||||
header = "More Info",
|
||||
footer = function(info)
|
||||
return info.column.id
|
||||
end,
|
||||
columns = {
|
||||
{
|
||||
accessor_key = "visits",
|
||||
header = "Visits",
|
||||
footer = function(info)
|
||||
return info.column.id
|
||||
end,
|
||||
},
|
||||
{
|
||||
accessor_key = "status",
|
||||
header = "Status",
|
||||
footer = function(info)
|
||||
return info.column.id
|
||||
end,
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
{
|
||||
header = "Profile Progress",
|
||||
accessor_key = "progress",
|
||||
footer = function(info)
|
||||
return info.column.id
|
||||
end,
|
||||
},
|
||||
}
|
||||
end)
|
||||
|
||||
it("is drawn correctly", function()
|
||||
local table = Table({
|
||||
bufnr = bufnr,
|
||||
columns = grouped_columns,
|
||||
data = data,
|
||||
})
|
||||
|
||||
table:render()
|
||||
|
||||
h.assert_buf_lines(table.bufnr, {
|
||||
"┌───────────────────┬──────────────────────────┬────────────────┐",
|
||||
"│Name │Info │ │",
|
||||
"├─────────┬─────────┼───┬──────────────────────┤ │",
|
||||
"│ │ │ │More Info │ │",
|
||||
"│ │ │ ├──────┬───────────────┤ │",
|
||||
"│firstName│Last Name│Age│Visits│Status │Profile Progress│",
|
||||
"├─────────┼─────────┼───┼──────┼───────────────┼────────────────┤",
|
||||
"│tanner │linsley │24 │100 │In Relationship│50 │",
|
||||
"├─────────┼─────────┼───┼──────┼───────────────┼────────────────┤",
|
||||
"│tandy │miller │40 │40 │Single │80 │",
|
||||
"├─────────┼─────────┼───┼──────┼───────────────┼────────────────┤",
|
||||
"│joe │dirte │45 │20 │Complicated │10 │",
|
||||
"├─────────┼─────────┼───┼──────┼───────────────┼────────────────┤",
|
||||
"│firstName│lastName │age│visits│status │progress │",
|
||||
"│ │ │ ├──────┴───────────────┤ │",
|
||||
"│ │ │ │More Info │ │",
|
||||
"├─────────┴─────────┼───┴──────────────────────┤ │",
|
||||
"│Name │Info │ │",
|
||||
"└───────────────────┴──────────────────────────┴────────────────┘",
|
||||
})
|
||||
end)
|
||||
end)
|
||||
end)
|
||||
|
||||
describe("method :get_cell", function()
|
||||
it("returns nil on border", function()
|
||||
local table = Table({
|
||||
bufnr = bufnr,
|
||||
columns = { { accessor_key = "value" } },
|
||||
data = { { value = "Such Value!" } },
|
||||
})
|
||||
|
||||
table:render()
|
||||
|
||||
vim.api.nvim_win_set_cursor(winid, { 1, 5 })
|
||||
|
||||
local cell = table:get_cell()
|
||||
|
||||
eq(cell, nil)
|
||||
end)
|
||||
|
||||
it("works after shifting", function()
|
||||
local table = Table({
|
||||
bufnr = bufnr,
|
||||
columns = { { accessor_key = "value" } },
|
||||
data = { { id = 0, value = "Such Value!" } },
|
||||
})
|
||||
|
||||
table:render()
|
||||
|
||||
local cell
|
||||
|
||||
vim.api.nvim_win_set_cursor(winid, { 2, 5 })
|
||||
cell = table:get_cell()
|
||||
eq(type(cell), "table")
|
||||
eq(cell.row.original.id, 0)
|
||||
|
||||
table:render(2)
|
||||
|
||||
vim.api.nvim_win_set_cursor(winid, { 2, 5 })
|
||||
cell = table:get_cell()
|
||||
eq(type(cell), "nil")
|
||||
|
||||
vim.api.nvim_win_set_cursor(winid, { 3, 5 })
|
||||
cell = table:get_cell()
|
||||
eq(type(cell), "table")
|
||||
eq(cell.row.original.id, 0)
|
||||
end)
|
||||
|
||||
it("can take position", function()
|
||||
local table = Table({
|
||||
bufnr = bufnr,
|
||||
columns = {
|
||||
{ accessor_key = "id" },
|
||||
{ accessor_key = "value" },
|
||||
},
|
||||
data = {
|
||||
{ id = 1, value = "One" },
|
||||
{ id = 2, value = "Two" },
|
||||
},
|
||||
})
|
||||
|
||||
table:render()
|
||||
|
||||
local cell
|
||||
|
||||
vim.api.nvim_win_set_cursor(winid, { 2, 3 })
|
||||
cell = table:get_cell()
|
||||
eq(cell.get_value(), 1)
|
||||
|
||||
cell = table:get_cell({ 1, 1 })
|
||||
eq(cell.get_value(), "Two")
|
||||
end)
|
||||
end)
|
||||
|
||||
describe("method :refresh_cell", function()
|
||||
it("can truncate NuiText on refesh", function()
|
||||
local table = Table({
|
||||
bufnr = bufnr,
|
||||
columns = { { accessor_key = "value" } },
|
||||
data = { { value = "Such Value!" } },
|
||||
})
|
||||
|
||||
table:render()
|
||||
|
||||
h.assert_buf_lines(table.bufnr, {
|
||||
"┌───────────┐",
|
||||
"│Such Value!│",
|
||||
"└───────────┘",
|
||||
})
|
||||
|
||||
vim.api.nvim_win_set_cursor(winid, { 2, 5 })
|
||||
|
||||
local cell = table:get_cell()
|
||||
|
||||
cell.row.original.value = "Such Looooooog Value!"
|
||||
|
||||
table:refresh_cell(cell)
|
||||
|
||||
h.assert_buf_lines(table.bufnr, {
|
||||
"┌───────────┐",
|
||||
"│Such Loooo…│",
|
||||
"└───────────┘",
|
||||
})
|
||||
end)
|
||||
|
||||
it("can truncate NuiLine on refesh", function()
|
||||
local table = Table({
|
||||
bufnr = bufnr,
|
||||
columns = {
|
||||
{
|
||||
accessor_key = "value",
|
||||
cell = function(cell)
|
||||
return Line({ Text(tostring(cell.get_value()), "NuiTest"), Text(" years old") })
|
||||
end,
|
||||
},
|
||||
},
|
||||
data = { { value = 42 } },
|
||||
})
|
||||
|
||||
table:render()
|
||||
|
||||
h.assert_buf_lines(table.bufnr, {
|
||||
"┌────────────┐",
|
||||
"│42 years old│",
|
||||
"└────────────┘",
|
||||
})
|
||||
|
||||
vim.api.nvim_win_set_cursor(winid, { 2, 5 })
|
||||
|
||||
local cell = table:get_cell()
|
||||
|
||||
eq(type(cell), "table")
|
||||
|
||||
cell.row.original.value = 100
|
||||
|
||||
table:refresh_cell(cell)
|
||||
|
||||
h.assert_buf_lines(table.bufnr, {
|
||||
"┌────────────┐",
|
||||
"│100 years o…│",
|
||||
"└────────────┘",
|
||||
})
|
||||
end)
|
||||
end)
|
||||
end)
|
|
@ -0,0 +1,284 @@
|
|||
pcall(require, "luacov")
|
||||
|
||||
local Text = require("nui.text")
|
||||
local h = require("tests.helpers")
|
||||
local spy = require("luassert.spy")
|
||||
|
||||
local eq, tbl_omit = h.eq, h.tbl_omit
|
||||
|
||||
describe("nui.text", function()
|
||||
local multibyte_char
|
||||
|
||||
before_each(function()
|
||||
multibyte_char = "║"
|
||||
end)
|
||||
|
||||
it("can clone nui.text object", function()
|
||||
local hl_group = "NuiTextTest"
|
||||
|
||||
local t1 = Text("42", hl_group)
|
||||
|
||||
t1.extmark.id = 42
|
||||
local t2 = Text(t1)
|
||||
eq(t2:content(), t1:content())
|
||||
eq(t2.extmark, tbl_omit(t1.extmark, { "id" }))
|
||||
|
||||
t2.extmark.id = 42
|
||||
local t3 = Text(t2)
|
||||
eq(t3:content(), t2:content())
|
||||
eq(t3.extmark, tbl_omit(t2.extmark, { "id" }))
|
||||
end)
|
||||
|
||||
it("can clone nui.text object overriding extmark", function()
|
||||
local hl_group = "NuiTextTest"
|
||||
local hl_group_override = "NuiTextTestOverride"
|
||||
|
||||
local t1 = Text("42", hl_group)
|
||||
|
||||
t1.extmark.id = 42
|
||||
local t2 = Text(t1, hl_group_override)
|
||||
eq(t2:content(), t1:content())
|
||||
eq(t2.extmark, { hl_group = hl_group_override })
|
||||
|
||||
local t3 = Text(t2, { id = 42, hl_group = hl_group })
|
||||
eq(t3:content(), t2:content())
|
||||
eq(t3.extmark, { hl_group = hl_group })
|
||||
end)
|
||||
|
||||
describe("method :set", function()
|
||||
it("works", function()
|
||||
local hl_group = "NuiTextTest"
|
||||
local hl_group_override = "NuiTextTestOverride"
|
||||
|
||||
local text = Text("42", hl_group)
|
||||
|
||||
eq(text:content(), "42")
|
||||
eq(text:length(), 2)
|
||||
eq(text.extmark, {
|
||||
hl_group = hl_group,
|
||||
})
|
||||
|
||||
text.extmark.id = 42
|
||||
|
||||
text:set("3")
|
||||
eq(text:content(), "3")
|
||||
eq(text:length(), 1)
|
||||
eq(text.extmark, {
|
||||
hl_group = hl_group,
|
||||
id = 42,
|
||||
})
|
||||
|
||||
text:set("9", hl_group_override)
|
||||
eq(text:content(), "9")
|
||||
eq(text.extmark, {
|
||||
hl_group = hl_group_override,
|
||||
id = 42,
|
||||
})
|
||||
|
||||
text:set("11", { hl_group = hl_group })
|
||||
eq(text:content(), "11")
|
||||
eq(text.extmark, {
|
||||
hl_group = hl_group,
|
||||
id = 42,
|
||||
})
|
||||
|
||||
text.extmark.id = nil
|
||||
|
||||
text:set("42", { id = 42, hl_group = hl_group })
|
||||
eq(text:content(), "42")
|
||||
eq(text.extmark, { hl_group = hl_group })
|
||||
end)
|
||||
end)
|
||||
|
||||
describe("method :content", function()
|
||||
it("works", function()
|
||||
local content = "42"
|
||||
local text = Text(content)
|
||||
eq(text:content(), content)
|
||||
|
||||
local multibyte_content = multibyte_char
|
||||
local multibyte_text = Text(multibyte_content)
|
||||
eq(multibyte_text:content(), multibyte_content)
|
||||
end)
|
||||
end)
|
||||
|
||||
describe("method :length", function()
|
||||
it("works", function()
|
||||
local content = "42"
|
||||
local text = Text(content)
|
||||
eq(text:length(), 2)
|
||||
eq(text:length(), vim.fn.strlen(content))
|
||||
|
||||
local multibyte_content = multibyte_char
|
||||
local multibyte_text = Text(multibyte_content)
|
||||
eq(multibyte_text:length(), 3)
|
||||
eq(multibyte_text:length(), vim.fn.strlen(multibyte_content))
|
||||
end)
|
||||
end)
|
||||
|
||||
describe("method :width", function()
|
||||
it("works", function()
|
||||
local content = "42"
|
||||
local text = Text(content)
|
||||
eq(text:width(), 2)
|
||||
eq(text:width(), vim.fn.strwidth(content))
|
||||
|
||||
local multibyte_content = multibyte_char
|
||||
local multibyte_text = Text(multibyte_content)
|
||||
eq(multibyte_text:width(), 1)
|
||||
eq(multibyte_text:width(), vim.fn.strwidth(multibyte_content))
|
||||
end)
|
||||
end)
|
||||
|
||||
describe("method", function()
|
||||
local winid, bufnr
|
||||
local initial_lines
|
||||
|
||||
before_each(function()
|
||||
winid = vim.api.nvim_get_current_win()
|
||||
bufnr = vim.api.nvim_create_buf(false, true)
|
||||
|
||||
vim.api.nvim_win_set_buf(winid, bufnr)
|
||||
|
||||
initial_lines = { " 1", multibyte_char .. " 2", " 3" }
|
||||
end)
|
||||
|
||||
after_each(function()
|
||||
vim.api.nvim_buf_delete(bufnr, { force = true })
|
||||
end)
|
||||
|
||||
local function reset_lines(lines)
|
||||
initial_lines = lines or initial_lines
|
||||
vim.api.nvim_buf_set_lines(bufnr, 0, -1, false, initial_lines)
|
||||
end
|
||||
|
||||
describe(":highlight", function()
|
||||
local hl_group, ns, ns_id
|
||||
local linenr, byte_start
|
||||
local text
|
||||
|
||||
before_each(function()
|
||||
hl_group = "NuiTextTest"
|
||||
ns = "NuiTest"
|
||||
ns_id = vim.api.nvim_create_namespace(ns)
|
||||
end)
|
||||
|
||||
it("is applied with :render", function()
|
||||
reset_lines()
|
||||
linenr, byte_start = 1, 0
|
||||
text = Text("a", hl_group)
|
||||
text:render(bufnr, ns_id, linenr, byte_start)
|
||||
h.assert_highlight(bufnr, ns_id, linenr, text:content(), hl_group)
|
||||
end)
|
||||
|
||||
it("is applied with :render_char", function()
|
||||
reset_lines()
|
||||
linenr, byte_start = 1, 0
|
||||
text = Text(multibyte_char, hl_group)
|
||||
text:render_char(bufnr, ns_id, linenr, byte_start)
|
||||
h.assert_highlight(bufnr, ns_id, linenr, text:content(), hl_group)
|
||||
end)
|
||||
|
||||
it("can highlight existing buffer text", function()
|
||||
reset_lines()
|
||||
linenr, byte_start = 2, 0
|
||||
text = Text(initial_lines[linenr], hl_group)
|
||||
text:highlight(bufnr, ns_id, linenr, byte_start)
|
||||
h.assert_highlight(bufnr, ns_id, linenr, text:content(), hl_group)
|
||||
end)
|
||||
|
||||
it("does not create multiple extmarks", function()
|
||||
reset_lines()
|
||||
linenr, byte_start = 2, 0
|
||||
text = Text(initial_lines[linenr], hl_group)
|
||||
|
||||
text:highlight(bufnr, ns_id, linenr, byte_start)
|
||||
h.assert_highlight(bufnr, ns_id, linenr, text:content(), hl_group)
|
||||
text:highlight(bufnr, ns_id, linenr, byte_start)
|
||||
h.assert_highlight(bufnr, ns_id, linenr, text:content(), hl_group)
|
||||
text:highlight(bufnr, ns_id, linenr, byte_start)
|
||||
h.assert_highlight(bufnr, ns_id, linenr, text:content(), hl_group)
|
||||
end)
|
||||
end)
|
||||
|
||||
describe(":render", function()
|
||||
it("works on line with singlebyte characters", function()
|
||||
reset_lines()
|
||||
|
||||
local text = Text("a")
|
||||
|
||||
spy.on(text, "highlight")
|
||||
|
||||
text:render(bufnr, -1, 1, 1)
|
||||
|
||||
assert.spy(text.highlight).was_called(1)
|
||||
assert.spy(text.highlight).was_called_with(text, bufnr, -1, 1, 1)
|
||||
|
||||
h.assert_buf_lines(bufnr, {
|
||||
" a1",
|
||||
initial_lines[2],
|
||||
initial_lines[3],
|
||||
})
|
||||
end)
|
||||
|
||||
it("works on line with multibyte characters", function()
|
||||
reset_lines()
|
||||
|
||||
local text = Text("a")
|
||||
|
||||
spy.on(text, "highlight")
|
||||
|
||||
text:render(bufnr, -1, 2, vim.fn.strlen(multibyte_char))
|
||||
|
||||
assert.spy(text.highlight).was_called(1)
|
||||
assert.spy(text.highlight).was_called_with(text, bufnr, -1, 2, vim.fn.strlen(multibyte_char))
|
||||
|
||||
h.assert_buf_lines(bufnr, {
|
||||
initial_lines[1],
|
||||
multibyte_char .. "a2",
|
||||
initial_lines[3],
|
||||
})
|
||||
end)
|
||||
end)
|
||||
|
||||
describe(":render_char", function()
|
||||
it("works on line with singlebyte characters", function()
|
||||
reset_lines()
|
||||
|
||||
local text = Text("a")
|
||||
|
||||
spy.on(text, "highlight")
|
||||
|
||||
text:render_char(bufnr, -1, 1, 1)
|
||||
|
||||
assert.spy(text.highlight).was_called(1)
|
||||
assert.spy(text.highlight).was_called_with(text, bufnr, -1, 1, 1)
|
||||
|
||||
h.assert_buf_lines(bufnr, {
|
||||
" a1",
|
||||
initial_lines[2],
|
||||
initial_lines[3],
|
||||
})
|
||||
end)
|
||||
|
||||
it("works on line with multibyte characters", function()
|
||||
reset_lines()
|
||||
|
||||
local text = Text("a")
|
||||
|
||||
spy.on(text, "highlight")
|
||||
|
||||
text:render_char(bufnr, -1, 2, 1)
|
||||
|
||||
assert.spy(text.highlight).was_called(1)
|
||||
assert.spy(text.highlight).was_called_with(text, bufnr, -1, 2, vim.fn.strlen(multibyte_char))
|
||||
|
||||
h.assert_buf_lines(bufnr, {
|
||||
initial_lines[1],
|
||||
multibyte_char .. "a2",
|
||||
initial_lines[3],
|
||||
})
|
||||
end)
|
||||
end)
|
||||
end)
|
||||
end)
|
|
@ -0,0 +1,912 @@
|
|||
pcall(require, "luacov")
|
||||
|
||||
local Text = require("nui.text")
|
||||
local Tree = require("nui.tree")
|
||||
local h = require("tests.helpers")
|
||||
|
||||
local eq = h.eq
|
||||
|
||||
describe("nui.tree", function()
|
||||
local winid, bufnr
|
||||
|
||||
before_each(function()
|
||||
winid = vim.api.nvim_get_current_win()
|
||||
bufnr = vim.api.nvim_create_buf(false, true)
|
||||
|
||||
vim.api.nvim_win_set_buf(winid, bufnr)
|
||||
end)
|
||||
|
||||
after_each(function()
|
||||
vim.api.nvim_buf_delete(bufnr, { force = true })
|
||||
end)
|
||||
|
||||
describe("(#deprecated) o.winid", function()
|
||||
it("throws if missing", function()
|
||||
local ok, err = pcall(function()
|
||||
return Tree({})
|
||||
end)
|
||||
eq(ok, false)
|
||||
eq(type(string.match(err, "missing bufnr")), "string")
|
||||
end)
|
||||
|
||||
it("throws if invalid", function()
|
||||
local ok, err = pcall(function()
|
||||
return Tree({ winid = 999 })
|
||||
end)
|
||||
eq(ok, false)
|
||||
eq(type(string.match(err, "invalid winid ")), "string")
|
||||
end)
|
||||
|
||||
it("sets t.winid and t.bufnr properly", function()
|
||||
local tree = Tree({ winid = winid })
|
||||
|
||||
eq(tree.winid, winid)
|
||||
eq(tree.bufnr, bufnr)
|
||||
end)
|
||||
end)
|
||||
|
||||
describe("o.bufnr", function()
|
||||
it("throws if missing", function()
|
||||
local ok, err = pcall(function()
|
||||
return Tree({})
|
||||
end)
|
||||
eq(ok, false)
|
||||
eq(type(string.match(err, "missing bufnr")), "string")
|
||||
end)
|
||||
|
||||
it("throws if invalid", function()
|
||||
local ok, err = pcall(function()
|
||||
return Tree({ bufnr = 999 })
|
||||
end)
|
||||
eq(ok, false)
|
||||
eq(type(string.match(err, "invalid bufnr ")), "string")
|
||||
end)
|
||||
|
||||
it("sets t.bufnr properly", function()
|
||||
local tree = Tree({ bufnr = bufnr })
|
||||
|
||||
eq(tree.winid, nil)
|
||||
eq(tree.bufnr, bufnr)
|
||||
end)
|
||||
end)
|
||||
|
||||
it("throws on duplicated node id", function()
|
||||
local ok, err = pcall(function()
|
||||
return Tree({
|
||||
bufnr = bufnr,
|
||||
nodes = {
|
||||
Tree.Node({ id = "id", text = "text" }),
|
||||
Tree.Node({ id = "id", text = "text" }),
|
||||
},
|
||||
})
|
||||
end)
|
||||
eq(ok, false)
|
||||
eq(type(err), "string")
|
||||
end)
|
||||
|
||||
it("sets default buf options emulating scratch-buffer", function()
|
||||
local tree = Tree({ bufnr = bufnr })
|
||||
|
||||
h.assert_buf_options(tree.bufnr, {
|
||||
bufhidden = "hide",
|
||||
buflisted = false,
|
||||
buftype = "nofile",
|
||||
swapfile = false,
|
||||
})
|
||||
end)
|
||||
|
||||
describe("(#deprecated) o.win_options", function()
|
||||
it("sets default values for handling folds", function()
|
||||
local tree = Tree({ winid = winid })
|
||||
|
||||
h.assert_win_options(tree.winid, {
|
||||
foldmethod = "manual",
|
||||
foldcolumn = "0",
|
||||
wrap = false,
|
||||
})
|
||||
end)
|
||||
|
||||
it("sets values", function()
|
||||
local initial_statusline = vim.api.nvim_win_get_option(winid, "statusline")
|
||||
|
||||
local statusline = "test: win_options " .. math.random()
|
||||
local tree = Tree({
|
||||
winid = winid,
|
||||
win_options = {
|
||||
statusline = statusline,
|
||||
},
|
||||
})
|
||||
|
||||
h.assert_win_options(tree.winid, {
|
||||
statusline = statusline,
|
||||
})
|
||||
|
||||
vim.api.nvim_win_set_option(tree.winid, "statusline", initial_statusline)
|
||||
end)
|
||||
|
||||
it("has no effect if o.bufnr is present", function()
|
||||
local initial_statusline = vim.api.nvim_win_get_option(winid, "statusline")
|
||||
|
||||
Tree({
|
||||
bufnr = bufnr,
|
||||
win_options = {
|
||||
statusline = "test: win_options" .. math.random(),
|
||||
},
|
||||
})
|
||||
|
||||
h.assert_win_options(winid, {
|
||||
statusline = initial_statusline,
|
||||
})
|
||||
end)
|
||||
end)
|
||||
|
||||
it("sets t.ns_id if o.ns_id is string", function()
|
||||
local ns = "NuiTreeTest"
|
||||
local tree = Tree({ bufnr = bufnr, ns_id = ns })
|
||||
|
||||
local namespaces = vim.api.nvim_get_namespaces()
|
||||
|
||||
eq(tree.ns_id, namespaces[ns])
|
||||
end)
|
||||
|
||||
it("sets t.ns_id if o.ns_id is number", function()
|
||||
local ns = "NuiTreeTest"
|
||||
local ns_id = vim.api.nvim_create_namespace(ns)
|
||||
local tree = Tree({ bufnr = bufnr, ns_id = ns_id })
|
||||
|
||||
eq(tree.ns_id, ns_id)
|
||||
end)
|
||||
|
||||
it("uses o.get_node_id if provided", function()
|
||||
local node_d2 = Tree.Node({ key = "depth two" })
|
||||
local node_d1 = Tree.Node({ key = "depth one" }, { node_d2 })
|
||||
Tree({
|
||||
bufnr = bufnr,
|
||||
nodes = { node_d1 },
|
||||
get_node_id = function(node)
|
||||
return node.key
|
||||
end,
|
||||
})
|
||||
|
||||
eq(node_d1:get_id(), node_d1.key)
|
||||
eq(node_d2:get_id(), node_d2.key)
|
||||
end)
|
||||
|
||||
describe("default get_node_id", function()
|
||||
it("returns id using n.id", function()
|
||||
local node = Tree.Node({ id = "id", text = "text" })
|
||||
Tree({ bufnr = bufnr, nodes = { node } })
|
||||
|
||||
eq(node:get_id(), "-id")
|
||||
end)
|
||||
|
||||
it("returns id using parent_id + depth + n.text", function()
|
||||
local node_d2 = Tree.Node({ text = { "depth two a", Text("depth two b") } })
|
||||
local node_d1 = Tree.Node({ text = "depth one" }, { node_d2 })
|
||||
Tree({ bufnr = bufnr, nodes = { node_d1 } })
|
||||
|
||||
eq(node_d1:get_id(), string.format("-%s-%s", node_d1:get_depth(), node_d1.text))
|
||||
eq(
|
||||
node_d2:get_id(),
|
||||
string.format(
|
||||
"%s-%s-%s",
|
||||
node_d2:get_parent_id(),
|
||||
node_d2:get_depth(),
|
||||
table.concat({ node_d2.text[1], node_d2.text[2]:content() }, "-")
|
||||
)
|
||||
)
|
||||
end)
|
||||
|
||||
it("returns id using random number", function()
|
||||
math.randomseed(0)
|
||||
local expected_id = "-" .. math.random()
|
||||
math.randomseed(0)
|
||||
|
||||
local node = Tree.Node({})
|
||||
Tree({ bufnr = bufnr, nodes = { node } })
|
||||
|
||||
eq(node:get_id(), expected_id)
|
||||
end)
|
||||
end)
|
||||
|
||||
it("uses o.prepare_node if provided", function()
|
||||
local function prepare_node(node, parent_node)
|
||||
if not parent_node then
|
||||
return node.text
|
||||
end
|
||||
|
||||
return parent_node.text .. ":" .. node.text
|
||||
end
|
||||
|
||||
local nodes = {
|
||||
Tree.Node({ text = "a" }),
|
||||
Tree.Node({ text = "b" }, {
|
||||
Tree.Node({ text = "b-1" }),
|
||||
Tree.Node({ text = "b-2" }),
|
||||
}),
|
||||
Tree.Node({ text = "c" }),
|
||||
}
|
||||
|
||||
nodes[2]:expand()
|
||||
|
||||
local tree = Tree({
|
||||
bufnr = bufnr,
|
||||
nodes = nodes,
|
||||
prepare_node = prepare_node,
|
||||
})
|
||||
|
||||
tree:render()
|
||||
|
||||
h.assert_buf_lines(tree.bufnr, {
|
||||
"a",
|
||||
"b",
|
||||
"b:b-1",
|
||||
"b:b-2",
|
||||
"c",
|
||||
})
|
||||
end)
|
||||
|
||||
describe("default prepare_node", function()
|
||||
it("throws if missing n.text", function()
|
||||
local nodes = {
|
||||
Tree.Node({ txt = "a" }),
|
||||
Tree.Node({ txt = "b" }),
|
||||
Tree.Node({ txt = "c" }),
|
||||
}
|
||||
|
||||
local tree = Tree({
|
||||
bufnr = bufnr,
|
||||
nodes = nodes,
|
||||
})
|
||||
|
||||
local ok, err = pcall(tree.render, tree)
|
||||
eq(ok, false)
|
||||
eq(type(err), "string")
|
||||
end)
|
||||
|
||||
it("uses n.text", function()
|
||||
local nodes = {
|
||||
Tree.Node({ text = "a" }),
|
||||
Tree.Node({ text = { "b-1", "b-2" } }),
|
||||
Tree.Node({ text = "c" }),
|
||||
}
|
||||
|
||||
local tree = Tree({
|
||||
bufnr = bufnr,
|
||||
nodes = nodes,
|
||||
})
|
||||
|
||||
tree:render()
|
||||
|
||||
h.assert_buf_lines(tree.bufnr, {
|
||||
" a",
|
||||
" b-1",
|
||||
" b-2",
|
||||
" c",
|
||||
})
|
||||
end)
|
||||
|
||||
it("renders arrow if children are present", function()
|
||||
local nodes = {
|
||||
Tree.Node({ text = "a" }),
|
||||
Tree.Node({ text = "b" }, {
|
||||
Tree.Node({ text = "b-1" }),
|
||||
Tree.Node({ text = { "b-2", "b-3" } }),
|
||||
}),
|
||||
Tree.Node({ text = "c" }),
|
||||
}
|
||||
local tree = Tree({
|
||||
bufnr = bufnr,
|
||||
nodes = nodes,
|
||||
})
|
||||
|
||||
tree:render()
|
||||
|
||||
h.assert_buf_lines(tree.bufnr, {
|
||||
" a",
|
||||
" b",
|
||||
" c",
|
||||
})
|
||||
|
||||
nodes[2]:expand()
|
||||
tree:render()
|
||||
|
||||
h.assert_buf_lines(tree.bufnr, {
|
||||
" a",
|
||||
" b",
|
||||
" b-1",
|
||||
" b-2",
|
||||
" b-3",
|
||||
" c",
|
||||
})
|
||||
end)
|
||||
end)
|
||||
|
||||
describe("method :get_node", function()
|
||||
it("can get node under cursor", function()
|
||||
local nodes = {
|
||||
Tree.Node({ text = "a" }),
|
||||
Tree.Node({ text = "b" }),
|
||||
Tree.Node({ text = "c" }),
|
||||
}
|
||||
|
||||
local tree = Tree({
|
||||
bufnr = bufnr,
|
||||
nodes = nodes,
|
||||
})
|
||||
|
||||
tree:render()
|
||||
|
||||
local linenr = 3
|
||||
|
||||
vim.api.nvim_win_set_cursor(winid, { linenr, 0 })
|
||||
|
||||
eq({ tree:get_node() }, { nodes[3], linenr, linenr })
|
||||
end)
|
||||
|
||||
it("can get node with id", function()
|
||||
local b_node_children = {
|
||||
Tree.Node({ text = "b-1" }),
|
||||
Tree.Node({ text = { "b-2", "b-3" } }),
|
||||
}
|
||||
|
||||
local nodes = {
|
||||
Tree.Node({ text = "a" }),
|
||||
Tree.Node({ text = "b" }, b_node_children),
|
||||
Tree.Node({ text = "c" }),
|
||||
}
|
||||
|
||||
local tree = Tree({
|
||||
bufnr = bufnr,
|
||||
nodes = nodes,
|
||||
get_node_id = function(node)
|
||||
return type(node.text) == "table" and table.concat(node.text, "-") or node.text
|
||||
end,
|
||||
})
|
||||
|
||||
tree:render()
|
||||
|
||||
eq({ tree:get_node("b") }, { nodes[2], 2, 2 })
|
||||
|
||||
tree:get_node("b"):expand()
|
||||
tree:render()
|
||||
|
||||
eq({ tree:get_node("b-2-b-3") }, { b_node_children[2], 4, 5 })
|
||||
end)
|
||||
|
||||
it("can get node on linenr", function()
|
||||
local b_node_children = {
|
||||
Tree.Node({ id = "b-1-b-2", text = { "b-1", "b-2" } }),
|
||||
}
|
||||
|
||||
local nodes = {
|
||||
Tree.Node({ text = "a" }),
|
||||
Tree.Node({ text = "b" }, b_node_children),
|
||||
Tree.Node({ text = "c" }),
|
||||
}
|
||||
|
||||
local tree = Tree({
|
||||
bufnr = bufnr,
|
||||
nodes = nodes,
|
||||
})
|
||||
|
||||
tree:render()
|
||||
|
||||
eq({ tree:get_node(1) }, { nodes[1], 1, 1 })
|
||||
|
||||
tree:get_node(2):expand()
|
||||
tree:render()
|
||||
|
||||
eq({ tree:get_node(3) }, { b_node_children[1], 3, 4 })
|
||||
eq({ tree:get_node(4) }, { b_node_children[1], 3, 4 })
|
||||
end)
|
||||
end)
|
||||
|
||||
describe("method :get_nodes", function()
|
||||
it("can get nodes at root", function()
|
||||
local nodes = {
|
||||
Tree.Node({ text = "a" }),
|
||||
Tree.Node({ text = "b" }, {
|
||||
Tree.Node({ text = "b-1" }),
|
||||
}),
|
||||
}
|
||||
|
||||
local tree = Tree({
|
||||
bufnr = bufnr,
|
||||
nodes = nodes,
|
||||
get_node_id = function(node)
|
||||
return node.text
|
||||
end,
|
||||
})
|
||||
|
||||
eq(tree:get_nodes(), nodes)
|
||||
end)
|
||||
|
||||
it("can get nodes under parent node", function()
|
||||
local child_nodes = {
|
||||
Tree.Node({ text = "b-1" }),
|
||||
}
|
||||
|
||||
local tree = Tree({
|
||||
bufnr = bufnr,
|
||||
nodes = {
|
||||
Tree.Node({ text = "a" }),
|
||||
Tree.Node({ text = "b" }, child_nodes),
|
||||
},
|
||||
get_node_id = function(node)
|
||||
return node.text
|
||||
end,
|
||||
})
|
||||
|
||||
eq(tree:get_nodes("b"), child_nodes)
|
||||
end)
|
||||
end)
|
||||
|
||||
describe("method :add_node", function()
|
||||
it("throw if invalid parent_id", function()
|
||||
local tree = Tree({
|
||||
bufnr = bufnr,
|
||||
nodes = {
|
||||
Tree.Node({ text = "x" }),
|
||||
},
|
||||
})
|
||||
|
||||
local ok, err = pcall(tree.add_node, tree, Tree.Node({ text = "y" }), "invalid_parent_id")
|
||||
eq(ok, false)
|
||||
eq(type(err), "string")
|
||||
end)
|
||||
|
||||
it("can add node at root", function()
|
||||
local tree = Tree({
|
||||
bufnr = bufnr,
|
||||
nodes = {
|
||||
Tree.Node({ text = "x" }),
|
||||
},
|
||||
})
|
||||
|
||||
tree:add_node(Tree.Node({ text = "y" }))
|
||||
|
||||
tree:render()
|
||||
|
||||
h.assert_buf_lines(tree.bufnr, {
|
||||
" x",
|
||||
" y",
|
||||
})
|
||||
|
||||
tree:add_node(Tree.Node({ text = "z" }))
|
||||
|
||||
tree:render()
|
||||
|
||||
h.assert_buf_lines(tree.bufnr, {
|
||||
" x",
|
||||
" y",
|
||||
" z",
|
||||
})
|
||||
end)
|
||||
|
||||
it("can add node under parent node", function()
|
||||
local nodes = {
|
||||
Tree.Node({ text = "a" }),
|
||||
Tree.Node({ text = "b" }, {
|
||||
Tree.Node({ text = "b-1" }),
|
||||
}),
|
||||
Tree.Node({ text = "c" }),
|
||||
}
|
||||
|
||||
local tree = Tree({
|
||||
bufnr = bufnr,
|
||||
nodes = nodes,
|
||||
get_node_id = function(node)
|
||||
return node.text
|
||||
end,
|
||||
})
|
||||
|
||||
tree:add_node(Tree.Node({ text = "b-2" }), "b")
|
||||
|
||||
tree:get_node("b"):expand()
|
||||
|
||||
tree:add_node(Tree.Node({ text = "c-1" }), "c")
|
||||
|
||||
tree:get_node("c"):expand()
|
||||
|
||||
tree:render()
|
||||
|
||||
h.assert_buf_lines(tree.bufnr, {
|
||||
" a",
|
||||
" b",
|
||||
" b-1",
|
||||
" b-2",
|
||||
" c",
|
||||
" c-1",
|
||||
})
|
||||
end)
|
||||
end)
|
||||
|
||||
describe("method :set_nodes", function()
|
||||
it("throw if invalid parent_id", function()
|
||||
local tree = Tree({
|
||||
bufnr = bufnr,
|
||||
nodes = {
|
||||
Tree.Node({ text = "x" }),
|
||||
},
|
||||
})
|
||||
|
||||
local ok, err = pcall(tree.set_nodes, tree, {}, "invalid_parent_id")
|
||||
eq(ok, false)
|
||||
eq(type(err), "string")
|
||||
end)
|
||||
|
||||
it("can set nodes at root", function()
|
||||
local tree = Tree({
|
||||
bufnr = bufnr,
|
||||
nodes = {
|
||||
Tree.Node({ text = "x" }),
|
||||
},
|
||||
})
|
||||
|
||||
tree:set_nodes({
|
||||
Tree.Node({ text = "a" }),
|
||||
Tree.Node({ text = "b" }),
|
||||
})
|
||||
|
||||
tree:render()
|
||||
|
||||
h.assert_buf_lines(tree.bufnr, {
|
||||
" a",
|
||||
" b",
|
||||
})
|
||||
|
||||
tree:set_nodes({
|
||||
Tree.Node({ text = "c" }),
|
||||
})
|
||||
|
||||
tree:render()
|
||||
|
||||
h.assert_buf_lines(tree.bufnr, {
|
||||
" c",
|
||||
})
|
||||
end)
|
||||
|
||||
it("can set nodes under parent node", function()
|
||||
local nodes = {
|
||||
Tree.Node({ text = "a" }),
|
||||
Tree.Node({ text = "b" }, {
|
||||
Tree.Node({ text = "b-1" }),
|
||||
}),
|
||||
Tree.Node({ text = "c" }),
|
||||
}
|
||||
|
||||
local tree = Tree({
|
||||
bufnr = bufnr,
|
||||
nodes = nodes,
|
||||
get_node_id = function(node)
|
||||
return node.text
|
||||
end,
|
||||
})
|
||||
|
||||
tree:set_nodes({
|
||||
Tree.Node({ text = "b-2" }),
|
||||
}, "b")
|
||||
|
||||
tree:get_node("b"):expand()
|
||||
|
||||
tree:set_nodes({
|
||||
Tree.Node({ text = "c-1" }),
|
||||
Tree.Node({ text = "c-2" }),
|
||||
}, "c")
|
||||
|
||||
tree:get_node("c"):expand()
|
||||
|
||||
tree:render()
|
||||
|
||||
h.assert_buf_lines(tree.bufnr, {
|
||||
" a",
|
||||
" b",
|
||||
" b-2",
|
||||
" c",
|
||||
" c-1",
|
||||
" c-2",
|
||||
})
|
||||
end)
|
||||
end)
|
||||
|
||||
describe("method :remove_node", function()
|
||||
it("can remove node w/o parent", function()
|
||||
local nodes = {
|
||||
Tree.Node({ text = "a" }),
|
||||
Tree.Node({ text = "b" }, {
|
||||
Tree.Node({ text = "b-1" }),
|
||||
}),
|
||||
Tree.Node({ text = "c" }),
|
||||
}
|
||||
|
||||
local tree = Tree({
|
||||
bufnr = bufnr,
|
||||
nodes = nodes,
|
||||
get_node_id = function(node)
|
||||
return node.text
|
||||
end,
|
||||
})
|
||||
|
||||
tree:remove_node("a")
|
||||
|
||||
tree:get_node("b"):expand()
|
||||
|
||||
tree:render()
|
||||
|
||||
eq(
|
||||
vim.tbl_map(function(node)
|
||||
return node:get_id()
|
||||
end, tree:get_nodes()),
|
||||
{ "b", "c" }
|
||||
)
|
||||
|
||||
h.assert_buf_lines(tree.bufnr, {
|
||||
" b",
|
||||
" b-1",
|
||||
" c",
|
||||
})
|
||||
end)
|
||||
|
||||
it("can remove node w/ parent", function()
|
||||
local nodes = {
|
||||
Tree.Node({ text = "a" }),
|
||||
Tree.Node({ text = "b" }, {
|
||||
Tree.Node({ text = "b-1" }),
|
||||
}),
|
||||
Tree.Node({ text = "c" }),
|
||||
}
|
||||
|
||||
local tree = Tree({
|
||||
bufnr = bufnr,
|
||||
nodes = nodes,
|
||||
get_node_id = function(node)
|
||||
return node.text
|
||||
end,
|
||||
})
|
||||
|
||||
tree:remove_node("b-1")
|
||||
|
||||
tree:render()
|
||||
|
||||
eq(tree:get_node("b"):get_child_ids(), {})
|
||||
|
||||
h.assert_buf_lines(tree.bufnr, {
|
||||
" a",
|
||||
" b",
|
||||
" c",
|
||||
})
|
||||
end)
|
||||
|
||||
it("removes children nodes recursively", function()
|
||||
local nodes = {
|
||||
Tree.Node({ text = "a" }, {
|
||||
Tree.Node({ text = "a-1" }, {
|
||||
Tree.Node({ text = "a-1-x" }),
|
||||
}),
|
||||
}),
|
||||
}
|
||||
local tree = Tree({
|
||||
bufnr = bufnr,
|
||||
nodes = nodes,
|
||||
get_node_id = function(node)
|
||||
return node.text
|
||||
end,
|
||||
})
|
||||
h.neq(tree:get_node("a"), nil)
|
||||
h.neq(tree:get_node("a-1"), nil)
|
||||
h.neq(tree:get_node("a-1-x"), nil)
|
||||
|
||||
tree:remove_node("a")
|
||||
|
||||
eq(tree:get_node("a"), nil)
|
||||
eq(tree:get_node("a-1"), nil)
|
||||
eq(tree:get_node("a-1-x"), nil)
|
||||
end)
|
||||
end)
|
||||
|
||||
describe("method :render", function()
|
||||
it("handles unexpected case of missing node", function()
|
||||
local nodes = {
|
||||
Tree.Node({ text = "a" }),
|
||||
Tree.Node({ text = "b" }),
|
||||
Tree.Node({ text = "c" }),
|
||||
}
|
||||
|
||||
local tree = Tree({
|
||||
bufnr = bufnr,
|
||||
nodes = nodes,
|
||||
get_node_id = function(node)
|
||||
return node.text
|
||||
end,
|
||||
})
|
||||
|
||||
-- this should not happen normally
|
||||
tree.nodes.by_id["a"] = nil
|
||||
|
||||
tree:render()
|
||||
|
||||
h.assert_buf_lines(tree.bufnr, {
|
||||
" b",
|
||||
" c",
|
||||
})
|
||||
end)
|
||||
|
||||
it("skips node if o.prepare_node returns nil", function()
|
||||
local nodes = {
|
||||
Tree.Node({ text = "a" }),
|
||||
Tree.Node({ text = "b" }),
|
||||
Tree.Node({ text = "c" }),
|
||||
}
|
||||
|
||||
local tree = Tree({
|
||||
bufnr = bufnr,
|
||||
nodes = nodes,
|
||||
get_node_id = function(node)
|
||||
return node.text
|
||||
end,
|
||||
prepare_node = function(node)
|
||||
if node:get_id() == "b" then
|
||||
return nil
|
||||
end
|
||||
|
||||
return node.text
|
||||
end,
|
||||
})
|
||||
|
||||
tree:render()
|
||||
|
||||
h.assert_buf_lines(tree.bufnr, {
|
||||
"a",
|
||||
"c",
|
||||
})
|
||||
end)
|
||||
|
||||
it("supports param linenr_start", function()
|
||||
local b_node_children = {
|
||||
Tree.Node({ text = "b-1" }),
|
||||
Tree.Node({ text = "b-2" }),
|
||||
}
|
||||
local nodes = {
|
||||
Tree.Node({ text = "a" }),
|
||||
Tree.Node({ text = "b" }, b_node_children),
|
||||
}
|
||||
|
||||
vim.api.nvim_buf_set_lines(bufnr, 0, -1, false, {
|
||||
"NuiTreeTest",
|
||||
"",
|
||||
"NuiTreeTest",
|
||||
})
|
||||
|
||||
local tree = Tree({
|
||||
bufnr = bufnr,
|
||||
nodes = nodes,
|
||||
get_node_id = function(node)
|
||||
return node.text
|
||||
end,
|
||||
})
|
||||
|
||||
tree:render(2)
|
||||
|
||||
h.assert_buf_lines(tree.bufnr, {
|
||||
"NuiTreeTest",
|
||||
" a",
|
||||
" b",
|
||||
"NuiTreeTest",
|
||||
})
|
||||
|
||||
nodes[2]:expand()
|
||||
|
||||
tree:render()
|
||||
|
||||
h.assert_buf_lines(tree.bufnr, {
|
||||
"NuiTreeTest",
|
||||
" a",
|
||||
" b",
|
||||
" b-1",
|
||||
" b-2",
|
||||
"NuiTreeTest",
|
||||
})
|
||||
|
||||
nodes[2]:collapse()
|
||||
|
||||
tree:render(3)
|
||||
|
||||
h.assert_buf_lines(tree.bufnr, {
|
||||
"NuiTreeTest",
|
||||
"",
|
||||
" a",
|
||||
" b",
|
||||
"NuiTreeTest",
|
||||
})
|
||||
end)
|
||||
end)
|
||||
end)
|
||||
|
||||
describe("nui.tree.Node", function()
|
||||
describe("method :has_children", function()
|
||||
it("works before initialization", function()
|
||||
local node_wo_children = Tree.Node({ text = "a" })
|
||||
local node_w_children = Tree.Node({ text = "b" }, { Tree.Node({ text = "b-1" }) })
|
||||
|
||||
eq(node_wo_children._initialized, false)
|
||||
eq(node_wo_children:has_children(), false)
|
||||
|
||||
eq(node_w_children._initialized, false)
|
||||
eq(type(node_w_children.__children), "table")
|
||||
eq(node_w_children:has_children(), true)
|
||||
end)
|
||||
|
||||
it("works after initialization", function()
|
||||
local node_wo_children = Tree.Node({ text = "a" })
|
||||
local node_w_children = Tree.Node({ text = "b" }, { Tree.Node({ text = "b-1" }) })
|
||||
|
||||
Tree({
|
||||
bufnr = vim.api.nvim_win_get_buf(vim.api.nvim_get_current_win()),
|
||||
nodes = { node_wo_children, node_w_children },
|
||||
})
|
||||
|
||||
eq(node_wo_children._initialized, true)
|
||||
eq(node_wo_children:has_children(), false)
|
||||
|
||||
eq(node_w_children._initialized, true)
|
||||
eq(type(node_w_children.__children), "nil")
|
||||
eq(node_w_children:has_children(), true)
|
||||
end)
|
||||
end)
|
||||
|
||||
describe("method :expand", function()
|
||||
it("returns true if not already expanded", function()
|
||||
local node = Tree.Node({ text = "b" }, { Tree.Node({ text = "b-1" }) })
|
||||
eq(node:is_expanded(), false)
|
||||
eq(node:expand(), true)
|
||||
eq(node:is_expanded(), true)
|
||||
end)
|
||||
|
||||
it("returns false if already expanded", function()
|
||||
local node = Tree.Node({ text = "b" }, { Tree.Node({ text = "b-1" }) })
|
||||
node:expand()
|
||||
eq(node:is_expanded(), true)
|
||||
eq(node:expand(), false)
|
||||
eq(node:is_expanded(), true)
|
||||
end)
|
||||
|
||||
it("does work w/ zero child", function()
|
||||
local node = Tree.Node({ text = "a" }, {})
|
||||
eq(node:is_expanded(), false)
|
||||
eq(node:expand(), true)
|
||||
eq(node:is_expanded(), true)
|
||||
end)
|
||||
|
||||
it("does not work w/o children", function()
|
||||
local node = Tree.Node({ text = "a" })
|
||||
eq(node:is_expanded(), false)
|
||||
eq(node:expand(), false)
|
||||
eq(node:is_expanded(), false)
|
||||
end)
|
||||
end)
|
||||
|
||||
describe("method :collapse", function()
|
||||
it("returns true if not already collapsed", function()
|
||||
local node = Tree.Node({ text = "b" }, { Tree.Node({ text = "b-1" }) })
|
||||
node:expand()
|
||||
eq(node:is_expanded(), true)
|
||||
eq(node:collapse(), true)
|
||||
eq(node:is_expanded(), false)
|
||||
end)
|
||||
|
||||
it("returns false if already collapsed", function()
|
||||
local node = Tree.Node({ text = "b" }, { Tree.Node({ text = "b-1" }) })
|
||||
eq(node:is_expanded(), false)
|
||||
eq(node:collapse(), false)
|
||||
eq(node:is_expanded(), false)
|
||||
end)
|
||||
|
||||
it("does not work w/o children", function()
|
||||
local node = Tree.Node({ text = "a" })
|
||||
eq(node:is_expanded(), false)
|
||||
eq(node:collapse(), false)
|
||||
eq(node:is_expanded(), false)
|
||||
end)
|
||||
end)
|
||||
end)
|
Loading…
Add table
Add a link
Reference in a new issue