From 59ec556341331cf738784d58eac7c611ca080f7c Mon Sep 17 00:00:00 2001
From: shortcuts
Date: Thu, 29 Dec 2022 21:17:35 +0100
Subject: [PATCH] chore: init
fix: missing steps in README.md
fix: easier replace
fix: missing README.md mention
fix: test helpers
feat: add release script
chore: update README.md
feat: add setup script (#1)
this PR adds a setup script to make it easier for people to use the
template, it aims at automating the placeholder filenames/variables.
Full interactive test
feat: add doc generation check to CI (#2)
This PR adds a documentation check on the CI, so the user knows if they
forgot to generate latest version, or can see unwanted changes.
chore: typo in README
feat(cd): add release action (#4)
closes https://github.com/shortcuts/neovim-plugin-boilerplate/issues/3
feat(cd): remove homemade release script (#6)
Now that we have the automated release process, we don't need the custom
part anymore
chore(main): release 1.0.0 (#5)
:robot: I have created a release *beep* *boop*
---
* add doc generation check to CI
([#2](https://github.com/shortcuts/neovim-plugin-boilerplate/issues/2))
([15d4d14](https://github.com/shortcuts/neovim-plugin-boilerplate/commit/15d4d1462f0bf99349ddd626d8f1a4b1b95f8a14))
* add release script
([144c732](https://github.com/shortcuts/neovim-plugin-boilerplate/commit/144c732b598c01c52f81d89f085ff5a5aefe1a1f))
* add setup script
([#1](https://github.com/shortcuts/neovim-plugin-boilerplate/issues/1))
([fbffb71](https://github.com/shortcuts/neovim-plugin-boilerplate/commit/fbffb71deea4fafb4e76c5901fa263b155ab8e94))
* **cd:** add release action
([#4](https://github.com/shortcuts/neovim-plugin-boilerplate/issues/4))
([85cb257](https://github.com/shortcuts/neovim-plugin-boilerplate/commit/85cb257bfe0c2770364541044cfc478cecf58a2a))
* **cd:** remove homemade release script
([#6](https://github.com/shortcuts/neovim-plugin-boilerplate/issues/6))
([316de3d](https://github.com/shortcuts/neovim-plugin-boilerplate/commit/316de3d10be0f704bdfecde3d889efe9c2e57570))
* easier replace
([0d686ea](https://github.com/shortcuts/neovim-plugin-boilerplate/commit/0d686eab4a45c4437bfaa3fdf8365de305587dff))
* missing README.md mention
([97b16e0](https://github.com/shortcuts/neovim-plugin-boilerplate/commit/97b16e028283cc7a47421da518cd51c3db206427))
* missing steps in README.md
([6ac7c6f](https://github.com/shortcuts/neovim-plugin-boilerplate/commit/6ac7c6fab61fd9af968ad476161b06406692ca87))
* test helpers
([d65dd73](https://github.com/shortcuts/neovim-plugin-boilerplate/commit/d65dd73119ec466bdd99d9833f27c4f6a936fe1e))
---
This PR was generated with [Release
Please](https://github.com/googleapis/release-please). See
[documentation](https://github.com/googleapis/release-please#release-please).
Co-authored-by: github-actions[bot] <41898282+github-actions[bot]@users.noreply.github.com>
chore: add nightly to the CI (#7)
nightly is now usable on the CI! :D
chore: upgrade ci to run against Neovim 0.8.3 (#8)
update the CI to run against Neovim 0.8.3
chore: fix `bug report` issue template
fix: CI diff documentation (#9)
correctly get diff
chore: fix gh command in README.md
feat: make setup.sh more reliable
feat: template cleanup and improvements (#11)
better initial setup and remove useless functions
chore(main): release 1.1.0 (#10)
:robot: I have created a release *beep* *boop*
---
[1.1.0](https://github.com/shortcuts/neovim-plugin-boilerplate/compare/v1.0.0...v1.1.0)
(2023-03-26)
* make setup.sh more reliable
([6c2f360](https://github.com/shortcuts/neovim-plugin-boilerplate/commit/6c2f360be9acd1c747f9cce112c6a0205e76532c))
* template cleanup and improvements
([#11](https://github.com/shortcuts/neovim-plugin-boilerplate/issues/11))
([af2fcb0](https://github.com/shortcuts/neovim-plugin-boilerplate/commit/af2fcb0ffcac54eb9e4092bb860c22e29d2579dc))
* CI diff documentation
([#9](https://github.com/shortcuts/neovim-plugin-boilerplate/issues/9))
([c4b9836](https://github.com/shortcuts/neovim-plugin-boilerplate/commit/c4b98367f82a6fe47d7268ac7a3887643831eac8))
---
This PR was generated with [Release
Please](https://github.com/googleapis/release-please). See
[documentation](https://github.com/googleapis/release-please#release-please).
Co-authored-by: github-actions[bot] <41898282+github-actions[bot]@users.noreply.github.com>
chore: make CI run on v0.9.0 (#12)
chore: run CI on neovim 0.9.1 (#13)
update the CI to run on the latest version
feat!: improve template helpers and state manager (#14)
While the template is still a pretty good starting point, improvements
have been made on [my main
plugin](https://github.com/shortcuts/no-neck-pain.nvim) for better
developer experience, we can port them here.
chore(main): release 2.0.0 (#15)
:robot: I have created a release *beep* *boop*
---
[2.0.0](https://github.com/shortcuts/neovim-plugin-boilerplate/compare/v1.1.0...v2.0.0)
(2024-03-15)
* improve template helpers and state manager
([#14](https://github.com/shortcuts/neovim-plugin-boilerplate/issues/14))
* improve template helpers and state manager
([#14](https://github.com/shortcuts/neovim-plugin-boilerplate/issues/14))
([9cc87ad](https://github.com/shortcuts/neovim-plugin-boilerplate/commit/9cc87add9fffd7e54b9f37573ed105f2234c7ccd))
---
This PR was generated with [Release
Please](https://github.com/googleapis/release-please). See
[documentation](https://github.com/googleapis/release-please#release-please).
Co-authored-by: github-actions[bot] <41898282+github-actions[bot]@users.noreply.github.com>
feat(ci): add luals checks on CI (#16)
[apply
recommendations](https://www.reddit.com/r/neovim/comments/1bfdp9m/comment/kv2ljvo/?utm_source=share&utm_medium=web3x&utm_name=web3xcss&utm_term=1&utm_content=share_button)
chore(main): release 2.1.0 (#17)
:robot: I have created a release *beep* *boop*
---
[2.1.0](https://github.com/shortcuts/neovim-plugin-boilerplate/compare/v2.0.0...v2.1.0)
(2024-03-16)
* **ci:** add luals checks on CI
([#16](https://github.com/shortcuts/neovim-plugin-boilerplate/issues/16))
([2d0ecc4](https://github.com/shortcuts/neovim-plugin-boilerplate/commit/2d0ecc406f7b8a2c4fab5a7ed83967f6a35cbd5d))
---
This PR was generated with [Release
Please](https://github.com/googleapis/release-please). See
[documentation](https://github.com/googleapis/release-please#release-please).
Co-authored-by: github-actions[bot] <41898282+github-actions[bot]@users.noreply.github.com>
feat(ci): bump stylua (#18)
bump stylua-action dependency
chore(main): release 2.2.0 (#19)
:robot: I have created a release *beep* *boop*
---
[2.2.0](https://github.com/shortcuts/neovim-plugin-boilerplate/compare/v2.1.0...v2.2.0)
(2024-03-18)
* **ci:** bump stylua
([#18](https://github.com/shortcuts/neovim-plugin-boilerplate/issues/18))
([d97ea98](https://github.com/shortcuts/neovim-plugin-boilerplate/commit/d97ea98e85fb55a57e2ff9618982261e7d1a33d0))
---
This PR was generated with [Release
Please](https://github.com/googleapis/release-please). See
[documentation](https://github.com/googleapis/release-please#release-please).
Co-authored-by: github-actions[bot] <41898282+github-actions[bot]@users.noreply.github.com>
chore: run on nvim 10 (#20)
feat!: renew template (#22)
Hey, as I keep working on
https://github.com/shortcuts/no-neck-pain.nvim, I discover new stuff,
tools, good lua practices and some fixes here and there, so I usually
port them to this template.
Hopefully this will help someone getting started!
close https://github.com/shortcuts/neovim-plugin-boilerplate/issues/21
chore(main): release 3.0.0 (#23)
:robot: I have created a release *beep* *boop*
---
[3.0.0](https://github.com/shortcuts/neovim-plugin-boilerplate/compare/v2.2.0...v3.0.0)
(2024-09-25)
* renew template
([#22](https://github.com/shortcuts/neovim-plugin-boilerplate/issues/22))
* renew template
([#22](https://github.com/shortcuts/neovim-plugin-boilerplate/issues/22))
([ca72698](https://github.com/shortcuts/neovim-plugin-boilerplate/commit/ca726988e6711508ada1ee0e554824827d00e3be))
---
This PR was generated with [Release
Please](https://github.com/googleapis/release-please). See
[documentation](https://github.com/googleapis/release-please#release-please).
Co-authored-by: github-actions[bot] <41898282+github-actions[bot]@users.noreply.github.com>
chore: add funding file
chore: update template for key-analyzer
chore: add config options
feat: Update README.md with plugin features and usage
chore: update README
---
.github/CODEOWNERS | 1 +
.github/CODEOWNERS_TEMPLATE | 1 +
.github/ISSUE_TEMPLATE/Bug_report.yml | 58 ++++
.../ISSUE_TEMPLATE/Bug_report_template.yml | 58 ++++
.github/ISSUE_TEMPLATE/Feature_request.md | 13 +
.github/PULL_REQUEST_TEMPLATE.md | 7 +
.github/workflows/main.yml | 165 ++++++++++++
.gitignore | 3 +
.luacheckrc | 4 +
.luarc.json | 11 +
CHANGELOG.md | 2 +
FUNDING.yml | 1 +
LICENSE | 21 ++
Makefile | 55 ++++
README.md | 112 ++++++++
doc/key-analyzer.txt | 46 ++++
doc/tags | 5 +
lua/key-analyzer/config.lua | 59 ++++
lua/key-analyzer/init.lua | 95 +++++++
lua/key-analyzer/main.lua | 254 ++++++++++++++++++
lua/key-analyzer/util/log.lua | 87 ++++++
plugin/key-analyzer.lua | 6 +
scripts/minimal_init.lua | 15 ++
scripts/setup.sh | 79 ++++++
stylua.toml | 5 +
tests/helpers.lua | 193 +++++++++++++
tests/test_API.lua | 52 ++++
27 files changed, 1408 insertions(+)
create mode 100644 .github/CODEOWNERS
create mode 100644 .github/CODEOWNERS_TEMPLATE
create mode 100644 .github/ISSUE_TEMPLATE/Bug_report.yml
create mode 100644 .github/ISSUE_TEMPLATE/Bug_report_template.yml
create mode 100644 .github/ISSUE_TEMPLATE/Feature_request.md
create mode 100644 .github/PULL_REQUEST_TEMPLATE.md
create mode 100644 .github/workflows/main.yml
create mode 100644 .gitignore
create mode 100644 .luacheckrc
create mode 100644 .luarc.json
create mode 100644 CHANGELOG.md
create mode 100644 FUNDING.yml
create mode 100644 LICENSE
create mode 100644 Makefile
create mode 100644 README.md
create mode 100644 doc/key-analyzer.txt
create mode 100644 doc/tags
create mode 100644 lua/key-analyzer/config.lua
create mode 100644 lua/key-analyzer/init.lua
create mode 100644 lua/key-analyzer/main.lua
create mode 100644 lua/key-analyzer/util/log.lua
create mode 100644 plugin/key-analyzer.lua
create mode 100644 scripts/minimal_init.lua
create mode 100755 scripts/setup.sh
create mode 100644 stylua.toml
create mode 100644 tests/helpers.lua
create mode 100644 tests/test_API.lua
diff --git a/.github/CODEOWNERS b/.github/CODEOWNERS
new file mode 100644
index 0000000..fba448b
--- /dev/null
+++ b/.github/CODEOWNERS
@@ -0,0 +1 @@
+* @shortcuts
diff --git a/.github/CODEOWNERS_TEMPLATE b/.github/CODEOWNERS_TEMPLATE
new file mode 100644
index 0000000..e37a330
--- /dev/null
+++ b/.github/CODEOWNERS_TEMPLATE
@@ -0,0 +1 @@
+* @meznaric
diff --git a/.github/ISSUE_TEMPLATE/Bug_report.yml b/.github/ISSUE_TEMPLATE/Bug_report.yml
new file mode 100644
index 0000000..cbe6d19
--- /dev/null
+++ b/.github/ISSUE_TEMPLATE/Bug_report.yml
@@ -0,0 +1,58 @@
+name: Bug Report
+description: File a bug report.
+title: '[bug]: '
+labels: ['bug', 'triage']
+body:
+ - type: markdown
+ attributes:
+ value: |
+ ## Please help us help you!
+
+ Before filing your issue, ask yourself:
+ - Is there an issue already opened for this bug?
+ - Can I reproduce it?
+
+ In doubt, you can also open [a discussion](https://github.com/shortcuts/neovim-plugin-boilerplate/discussions/new/choose).
+ - type: textarea
+ attributes:
+ label: Description
+ description: A clear and concise description of what the bug is.
+ validations:
+ required: true
+ - type: dropdown
+ id: nvim-version
+ attributes:
+ label: Neovim version
+ description: Which Neovim version are you using?
+ options:
+ - 0.9.x
+ - 0.10.x
+ - Nightly
+ - <= 0.9.x
+ validations:
+ required: true
+ - type: textarea
+ attributes:
+ label: Steps to reproduce
+ description: Write down the steps to reproduce the bug, please include any information that seems relevant for us to reproduce it properly
+ placeholder: |
+ 1. I enter nvim with files `...`
+ 2. I press the following keys `...`
+ 3. I have those plugins enabled that might conflict `...`
+ 4. See error
+ validations:
+ required: true
+ - type: textarea
+ id: logs
+ attributes:
+ label: Relevant log output
+ description: Please copy and paste any relevant log output. You can set `debug = true` in your neovim-plugin-boilerplate config and then use `:message` in order to see the logs.
+ render: shell
+ - type: checkboxes
+ attributes:
+ label: Self-service
+ description: |
+ If you feel like you could contribute to this issue, please check the box below. This would tell us and other people looking for contributions that someone's working on it.
+ options:
+ - label: I'd be willing to fix this bug myself.
+
diff --git a/.github/ISSUE_TEMPLATE/Bug_report_template.yml b/.github/ISSUE_TEMPLATE/Bug_report_template.yml
new file mode 100644
index 0000000..0bf0d73
--- /dev/null
+++ b/.github/ISSUE_TEMPLATE/Bug_report_template.yml
@@ -0,0 +1,58 @@
+name: Bug Report
+description: File a bug report.
+title: '[bug]: '
+labels: ['bug', 'triage']
+body:
+ - type: markdown
+ attributes:
+ value: |
+ ## Please help us help you!
+
+ Before filing your issue, ask yourself:
+ - Is there an issue already opened for this bug?
+ - Can I reproduce it?
+
+ In doubt, you can also open [a discussion](https://github.com/otiv/key-analyzer.nvim/discussions/new/choose).
+ - type: textarea
+ attributes:
+ label: Description
+ description: A clear and concise description of what the bug is.
+ validations:
+ required: true
+ - type: dropdown
+ id: nvim-version
+ attributes:
+ label: Neovim version
+ description: Which Neovim version are you using?
+ options:
+ - 0.9.x
+ - 0.10.x
+ - Nightly
+ - <= 0.9.x
+ validations:
+ required: true
+ - type: textarea
+ attributes:
+ label: Steps to reproduce
+ description: Write down the steps to reproduce the bug, please include any information that seems relevant for us to reproduce it properly
+ placeholder: |
+ 1. I enter nvim with files `...`
+ 2. I press the following keys `...`
+ 3. I have those plugins enabled that might conflict `...`
+ 4. See error
+ validations:
+ required: true
+ - type: textarea
+ id: logs
+ attributes:
+ label: Relevant log output
+ description: Please copy and paste any relevant log output. You can set `debug = true` in your key-analyzer.nvim config and then use `:message` in order to see the logs.
+ render: shell
+ - type: checkboxes
+ attributes:
+ label: Self-service
+ description: |
+ If you feel like you could contribute to this issue, please check the box below. This would tell us and other people looking for contributions that someone's working on it.
+ options:
+ - label: I'd be willing to fix this bug myself.
+
diff --git a/.github/ISSUE_TEMPLATE/Feature_request.md b/.github/ISSUE_TEMPLATE/Feature_request.md
new file mode 100644
index 0000000..8f8cf6c
--- /dev/null
+++ b/.github/ISSUE_TEMPLATE/Feature_request.md
@@ -0,0 +1,13 @@
+---
+name: Feature request
+about: Suggest anything that would make your life easier, or the plugin better.
+---
+
+## Describe the problem
+
+
+
+## Describe the solution
+
+
+
diff --git a/.github/PULL_REQUEST_TEMPLATE.md b/.github/PULL_REQUEST_TEMPLATE.md
new file mode 100644
index 0000000..656bf0c
--- /dev/null
+++ b/.github/PULL_REQUEST_TEMPLATE.md
@@ -0,0 +1,7 @@
+## 📃 Summary
+
+
+
+## 📸 Preview
+
+
diff --git a/.github/workflows/main.yml b/.github/workflows/main.yml
new file mode 100644
index 0000000..6e8dbe8
--- /dev/null
+++ b/.github/workflows/main.yml
@@ -0,0 +1,165 @@
+name: main
+
+on:
+ push:
+ branches: [main]
+ pull_request:
+ types: [opened, synchronize]
+
+env:
+ LUA_LS_VERSION: 3.7.4
+
+concurrency:
+ group: github.head_ref
+ cancel-in-progress: true
+
+jobs:
+ lint:
+ runs-on: ubuntu-latest
+ name: lint
+ steps:
+ - uses: actions/checkout@v4
+
+ - uses: JohnnyMorganz/stylua-action@v4
+ with:
+ token: ${{ secrets.GITHUB_TOKEN }}
+ version: latest
+ args: --check . -g '*.lua' -g '!deps/'
+
+ documentation:
+ runs-on: ubuntu-latest
+ name: documentation
+ steps:
+ - uses: actions/checkout@v4
+ with:
+ fetch-depth: 2
+
+ - name: setup neovim
+ uses: rhysd/action-setup-vim@v1
+ with:
+ neovim: true
+ version: v0.10.1
+
+ - name: generate documentation
+ run: make documentation-ci
+
+ - name: check docs diff
+ run: exit $(git status --porcelain doc | wc -l | tr -d " ")
+
+ tests:
+ needs:
+ - lint
+ - documentation
+ runs-on: ubuntu-latest
+ timeout-minutes: 1
+ strategy:
+ matrix:
+ neovim_version: ['v0.9.5', 'v0.10.1']
+
+ steps:
+ - uses: actions/checkout@v4
+
+ - run: date +%F > todays-date
+
+ - name: restore cache for today's nightly.
+ uses: actions/cache@v4
+ with:
+ path: _neovim
+ key: ${{ runner.os }}-x64-${{ hashFiles('todays-date') }}
+
+ - name: restore luals cache
+ uses: actions/cache@v4
+ id: cache
+ with:
+ path: .ci/lua-ls
+ key: ${{ env.LUA_LS_VERSION }}
+
+ - name: setup luals
+ if: ${{ steps.cache.outputs.cache-hit != 'true' }}
+ run: mkdir -p .ci/lua-ls && curl -sL "https://github.com/LuaLS/lua-language-server/releases/download/${{ env.LUA_LS_VERSION }}/lua-language-server-${{ env.LUA_LS_VERSION }}-linux-x64.tar.gz" | tar xzf - -C "${PWD}/.ci/lua-ls"
+
+ - name: setup neovim
+ uses: rhysd/action-setup-vim@v1
+ with:
+ neovim: true
+ version: ${{ matrix.neovim_version }}
+
+ - name: run luals
+ run: |
+ export PATH="${PWD}/.ci/lua-ls/bin:${PATH}"
+ nvim --version
+ make luals-ci
+
+ - name: run tests
+ run: make test-ci
+
+ tests-nightly:
+ needs:
+ - lint
+ - documentation
+ runs-on: ubuntu-latest
+ timeout-minutes: 1
+ continue-on-error: true
+
+ steps:
+ - uses: actions/checkout@v4
+
+ - run: date +%F > todays-date
+
+ - name: restore cache for today's nightly.
+ uses: actions/cache@v4
+ with:
+ path: _neovim
+ key: ${{ runner.os }}-x64-${{ hashFiles('todays-date') }}
+
+ - name: restore luals cache
+ uses: actions/cache@v4
+ id: cache
+ with:
+ path: .ci/lua-ls
+ key: ${{ env.LUA_LS_VERSION }}
+
+ - name: setup luals
+ if: ${{ steps.cache.outputs.cache-hit != 'true' }}
+ run: mkdir -p .ci/lua-ls && curl -sL "https://github.com/LuaLS/lua-language-server/releases/download/${{ env.LUA_LS_VERSION }}/lua-language-server-${{ env.LUA_LS_VERSION }}-linux-x64.tar.gz" | tar xzf - -C "${PWD}/.ci/lua-ls"
+
+ - name: setup neovim
+ uses: rhysd/action-setup-vim@v1
+ with:
+ neovim: true
+ version: nightly
+
+ - name: run luals
+ run: |
+ export PATH="${PWD}/.ci/lua-ls/bin:${PATH}"
+ nvim --version
+ make luals-ci
+
+ - name: run tests
+ run: make test-ci
+
+ release:
+ name: release
+ if: ${{ github.ref == 'refs/heads/main' }}
+ needs:
+ - tests
+ runs-on: ubuntu-latest
+ steps:
+ - uses: actions/checkout@v4
+
+ - uses: google-github-actions/release-please-action@v4
+ id: release
+ with:
+ release-type: simple
+ package-name: key-analyzer.nvim
+
+ - name: tag stable versions
+ if: ${{ steps.release.outputs.release_created }}
+ run: |
+ git config user.name github-actions[bot]
+ git config user.email github-actions[bot]@users.noreply.github.com
+ git remote add gh-token "https://${{ secrets.GITHUB_TOKEN }}@github.com/google-github-actions/release-please-action.git"
+ git tag -d stable || true
+ git push origin :stable || true
+ git tag -a stable -m "Last Stable Release"
+ git push origin stable
diff --git a/.gitignore b/.gitignore
new file mode 100644
index 0000000..929091c
--- /dev/null
+++ b/.gitignore
@@ -0,0 +1,3 @@
+deps
+**.DS_Store
+.ci
diff --git a/.luacheckrc b/.luacheckrc
new file mode 100644
index 0000000..084dc49
--- /dev/null
+++ b/.luacheckrc
@@ -0,0 +1,4 @@
+globals = { "vim", "MiniTest" }
+max_line_length = false
+
+exclude_files = { "deps" }
diff --git a/.luarc.json b/.luarc.json
new file mode 100644
index 0000000..1b8485f
--- /dev/null
+++ b/.luarc.json
@@ -0,0 +1,11 @@
+{
+ "runtime.version": "LuaJIT",
+ "diagnostics.globals": [
+ "vim",
+ "MiniTest"
+ ],
+ "workspace.library": [
+ "/usr/local/share/nvim/runtime/lua",
+ ".ci/neovim/share/nvim/runtime/lua"
+ ]
+}
diff --git a/CHANGELOG.md b/CHANGELOG.md
new file mode 100644
index 0000000..4dc68c6
--- /dev/null
+++ b/CHANGELOG.md
@@ -0,0 +1,2 @@
+# Changelog
+
diff --git a/FUNDING.yml b/FUNDING.yml
new file mode 100644
index 0000000..1771a28
--- /dev/null
+++ b/FUNDING.yml
@@ -0,0 +1 @@
+github: shortcuts
diff --git a/LICENSE b/LICENSE
new file mode 100644
index 0000000..cab851c
--- /dev/null
+++ b/LICENSE
@@ -0,0 +1,21 @@
+MIT License
+
+Copyright (c) 2022
+
+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.
diff --git a/Makefile b/Makefile
new file mode 100644
index 0000000..f4e0aa7
--- /dev/null
+++ b/Makefile
@@ -0,0 +1,55 @@
+.SUFFIXES:
+
+all: documentation lint luals test
+
+# runs all the test files.
+test:
+ make deps
+ nvim --version | head -n 1 && echo ''
+ nvim --headless --noplugin -u ./scripts/minimal_init.lua \
+ -c "lua require('mini.test').setup()" \
+ -c "lua MiniTest.run({ execute = { reporter = MiniTest.gen_reporter.stdout({ group_depth = 2 }) } })"
+
+# runs all the test files on the nightly version, `bob` must be installed.
+test-nightly:
+ bob use nightly
+ make test
+
+# runs all the test files on the 0.8.3 version, `bob` must be installed.
+test-0.8.3:
+ bob use 0.8.3
+ make test
+
+# installs `mini.nvim`, used for both the tests and documentation.
+deps:
+ @mkdir -p deps
+ git clone --depth 1 https://github.com/echasnovski/mini.nvim deps/mini.nvim
+
+# installs deps before running tests, useful for the CI.
+test-ci: deps test
+
+# generates the documentation.
+documentation:
+ nvim --headless --noplugin -u ./scripts/minimal_init.lua -c "lua require('mini.doc').generate()" -c "qa!"
+
+# installs deps before running the documentation generation, useful for the CI.
+documentation-ci: deps documentation
+
+# performs a lint check and fixes issue if possible, following the config in `stylua.toml`.
+lint:
+ stylua . -g '*.lua' -g '!deps/' -g '!nightly/'
+ luacheck plugin/ lua/
+
+luals-ci:
+ rm -rf .ci/lua-ls/log
+ lua-language-server --configpath .luarc.json --logpath .ci/lua-ls/log --check .
+ [ -f .ci/lua-ls/log/check.json ] && { cat .ci/lua-ls/log/check.json 2>/dev/null; exit 1; } || true
+
+luals:
+ mkdir -p .ci/lua-ls
+ curl -sL "https://github.com/LuaLS/lua-language-server/releases/download/3.7.4/lua-language-server-3.7.4-darwin-x64.tar.gz" | tar xzf - -C "${PWD}/.ci/lua-ls"
+ make luals-ci
+
+# setup
+setup:
+ ./scripts/setup.sh
diff --git a/README.md b/README.md
new file mode 100644
index 0000000..4db4bab
--- /dev/null
+++ b/README.md
@@ -0,0 +1,112 @@
+
+
key-analyzer.nvim
+
+
+
+ Ever wondered which mappings are free to be mapped? Now it's a easier.
+
+
+
+
+
+
+## ⚡️ Features
+
+- 🎹 Visual QWERTY keyboard layout showing your mapped and unmapped keys
+- 🔍 Analyze mappings for any prefix in any mode (normal, insert, visual, etc.)
+- 💡 Interactive hover tooltips showing the mapping details
+
+## 📋 Installation
+
+
+
+
+
+Package manager |
+Snippet |
+
+
+
+
+
+
+
+[folke/lazy.nvim](https://github.com/folke/lazy.nvim)
+
+ |
+
+
+```lua
+require("lazy").setup({
+ { "meznaric/key-analyzer.nvim", opts = {} },
+})
+```
+
+ |
+
+
+
+
+
+## Usage
+
+| Command | Description |
+|-------------|----------------------------|
+| `:KeyAnalyzer [mode]` | Shows keyboard analysis for the given prefix and mode. Mode defaults to normal ('n') if not specified. |
+| `:KeyAnalyzer ` | Show `` mappings |
+| `:KeyAnalyzer b` | Show mappings starting with `b*` |
+| `:KeyAnalyzer x i` | Show mappings starting with CTRL + M x in insert mode |
+
+
+
+`:KeyAnalyzer` calls this lua code:
+`require('key-analyzer').show(prefix, mode)` if you wish to map it yourself
+
+> **Tip:** Click or move to any key to see its mapping details
+
+## ⚙ Configuration
+
+
+**Note**: The options are also available in Neovim by calling `:h key-analyzer.options`
+
+```lua
+require("key-analyzer").setup({
+ -- Name of the command to use for the plugin
+ command_name = "KeyAnalyzer", -- or nil to disable the command
+
+ -- Customize the highlight groups
+ highlights = {
+ bracket_used = "KeyAnalyzerBracketUsed",
+ letter_used = "KeyAnalyzerLetterUsed",
+ bracket_unused = "KeyAnalyzerBracketUnused",
+ letter_unused = "KeyAnalyzerLetterUnused",
+ promo_highlight = "KeyAnalyzerPromo",
+
+ -- Set to false if you want to define highlights manually
+ define_default_highlights = true,
+ },
+})
+```
+
+## Limitations
+
+ - Not all maps may be shown. For example `*`, because these built in window maps are not returned by `vim.api.nvim_get_keymap(mode)`. Another example that will not show up are also fold maps (`z`).
+ - Currently only US-ANSII layout is supported, but feel free to open a pull request
+ - There is no differentiation between upper case and lower case letters, both will show on the visualisation
+ - Remember: Some keys may not actually be bindable, for example
+ - KeyAnalyzer retreives mappings with `vim.api.nvim_get_keymap(mode)`, so local buffer mappings are not shown
+
+## ⌨ Contributing
+
+PRs and issues are always welcome. Make sure to provide as much context as possible when opening one.
+
+Few ideas:
+ - Different keyboard layouts via options
+ - Add third parameter to a command `buffer` / `global`, where buffer calls `nvim_buf_get_keymap`
+ - Bring in [presets from which-key.nvim](https://github.com/folke/which-key.nvim/blob/main/lua/which-key/plugins/presets.lua) so that built-in mappings work, eg. ``, `z`, ...
+
+ Find it interesting? ![X Follow](https://img.shields.io/twitter/follow/OtivDev)
diff --git a/doc/key-analyzer.txt b/doc/key-analyzer.txt
new file mode 100644
index 0000000..780b329
--- /dev/null
+++ b/doc/key-analyzer.txt
@@ -0,0 +1,46 @@
+==============================================================================
+------------------------------------------------------------------------------
+ *KeyAnalyzer.toggle()*
+ `KeyAnalyzer.toggle`()
+Toggle the plugin by calling the `enable`/`disable` methods respectively.
+
+------------------------------------------------------------------------------
+ *KeyAnalyzer.enable()*
+ `KeyAnalyzer.enable`({scope})
+Initializes the plugin, sets event listeners and internal state.
+
+------------------------------------------------------------------------------
+ *KeyAnalyzer.disable()*
+ `KeyAnalyzer.disable`()
+Disables the plugin, clear highlight groups and autocmds, closes side buffers and resets the internal state.
+
+
+==============================================================================
+------------------------------------------------------------------------------
+ *KeyAnalyzer.options*
+ `KeyAnalyzer.options`
+KeyAnalyzer configuration with its default values.
+
+Type ~
+`(table)`
+Default values:
+>lua
+ KeyAnalyzer.options = {
+ -- Prints useful logs about what event are triggered, and reasons actions are executed.
+ debug = false,
+ }
+
+<
+------------------------------------------------------------------------------
+ *KeyAnalyzer.setup()*
+ `KeyAnalyzer.setup`({options})
+Define your key-analyzer setup.
+
+Parameters ~
+{options} `(table)` Module config table. See |KeyAnalyzer.options|.
+
+Usage ~
+`require("key-analyzer").setup()` (add `{}` with your |KeyAnalyzer.options| table)
+
+
+ vim:tw=78:ts=8:noet:ft=help:norl:
\ No newline at end of file
diff --git a/doc/tags b/doc/tags
new file mode 100644
index 0000000..22feac5
--- /dev/null
+++ b/doc/tags
@@ -0,0 +1,5 @@
+KeyAnalyzer.disable() key-analyzer.txt /*KeyAnalyzer.disable()*
+KeyAnalyzer.enable() key-analyzer.txt /*KeyAnalyzer.enable()*
+KeyAnalyzer.options key-analyzer.txt /*KeyAnalyzer.options*
+KeyAnalyzer.setup() key-analyzer.txt /*KeyAnalyzer.setup()*
+KeyAnalyzer.toggle() key-analyzer.txt /*KeyAnalyzer.toggle()*
diff --git a/lua/key-analyzer/config.lua b/lua/key-analyzer/config.lua
new file mode 100644
index 0000000..4101c24
--- /dev/null
+++ b/lua/key-analyzer/config.lua
@@ -0,0 +1,59 @@
+local log = require("key-analyzer.util.log")
+
+local KeyAnalyzer = {}
+
+--- KeyAnalyzer configuration with its default values.
+---
+---@type table
+--- Default values:
+---@eval return MiniDoc.afterlines_to_code(MiniDoc.current.eval_section)
+KeyAnalyzer.options = {
+ -- Prints useful logs about what event are triggered, and reasons actions are executed.
+ debug = false,
+ -- Name of the command to use for the plugin, leave empty or false to disable the command.
+ command_name = "KeyAnalyzer",
+ highlights = {
+ bracket_used = "KeyAnalyzerBracketUsed",
+ letter_used = "KeyAnalyzerLetterUsed",
+ bracket_unused = "KeyAnalyzerBracketUnused",
+ letter_unused = "KeyAnalyzerLetterUnused",
+ promo_highlight = "KeyAnalyzerPromo",
+ -- If you are using any of the built-in highlight groups you should leave this enabled
+ define_default_highlights = true,
+ },
+}
+
+---@private
+local defaults = vim.deepcopy(KeyAnalyzer.options)
+
+--- Defaults KeyAnalyzer options by merging user provided options with the default plugin values.
+---
+---@param options table Module config table. See |KeyAnalyzer.options|.
+---
+---@private
+function KeyAnalyzer.defaults(options)
+ KeyAnalyzer.options = vim.deepcopy(vim.tbl_deep_extend("keep", options or {}, defaults or {}))
+
+ -- let your user know that they provided a wrong value, this is reported when your plugin is executed.
+ assert(
+ type(KeyAnalyzer.options.debug) == "boolean",
+ "`debug` must be a boolean (`true` or `false`)."
+ )
+
+ return KeyAnalyzer.options
+end
+
+--- Define your key-analyzer setup.
+---
+---@param options table Module config table. See |KeyAnalyzer.options|.
+---
+---@usage `require("key-analyzer").setup()` (add `{}` with your |KeyAnalyzer.options| table)
+function KeyAnalyzer.setup(options)
+ KeyAnalyzer.options = KeyAnalyzer.defaults(options or {})
+
+ log.warn_deprecation(KeyAnalyzer.options)
+
+ return KeyAnalyzer.options
+end
+
+return KeyAnalyzer
diff --git a/lua/key-analyzer/init.lua b/lua/key-analyzer/init.lua
new file mode 100644
index 0000000..471ef74
--- /dev/null
+++ b/lua/key-analyzer/init.lua
@@ -0,0 +1,95 @@
+local main = require("key-analyzer.main")
+local config = require("key-analyzer.config")
+
+local KeyAnalyzer = {}
+
+--- Toggle the plugin by calling the `enable`/`disable` methods respectively.
+function KeyAnalyzer.show(mode, prefix)
+ if _G.KeyAnalyzer.config == nil then
+ _G.KeyAnalyzer.config = config.options
+ end
+
+ main.show_keyboard_map(mode, prefix)
+end
+
+-- setup KeyAnalyzer options and merge them with user provided ones.
+function KeyAnalyzer.setup(opts)
+ _G.KeyAnalyzer.config = config.setup(opts)
+
+ -- Define a command
+ if
+ _G.KeyAnalyzer.config.command_name ~= nil
+ and _G.KeyAnalyzer.config.command_name ~= ""
+ and _G.KeyAnalyzer.config.command_name ~= false
+ then
+ -- vim.api.nvim_create_user_command(_G.KeyAnalyzer.config.command_name, function()
+ -- require("key-analyzer").toggle()
+ -- end, {})
+ vim.api.nvim_create_user_command(_G.KeyAnalyzer.config.command_name, function(opts)
+ local args = vim.split(opts.args, " ")
+ local prefix = args[1]
+ local mode = args[2] or "n" -- Default to normal mode if not specified
+
+ if prefix == "" then
+ vim.notify(
+ "Please specify a prefix (e.g., '', 'm', etc)",
+ vim.log.levels.ERROR
+ )
+ return
+ end
+
+ -- Validate mode
+ local valid_modes =
+ { n = true, v = true, x = true, s = true, o = true, i = true, t = true, c = true }
+ if not valid_modes[mode] then
+ vim.notify(
+ "Invalid mode specified. Valid modes: n, v, x, s, o, i, t, c",
+ vim.log.levels.ERROR
+ )
+ return
+ end
+
+ if prefix == "leader" then
+ prefix = ""
+ end
+ if prefix == "" then
+ -- ToDo: Implement similar behaviour to: https://github.com/folke/which-key.nvim/blob/8badb359f7ab8711e2575ef75dfe6fbbd87e4821/lua/which-key/plugins/presets.lua#L111
+ vim.notify(
+ "Built in mappings for will not show as they are not returned by :maps"
+ )
+ end
+ if prefix == "z" then
+ vim.notify(
+ "Built in mappings for folds will not show as they are not returned by :maps"
+ )
+ end
+
+ main.show_keyboard_map(mode, prefix)
+ end, {
+ nargs = "+",
+ desc = "Analyze keyboard mappings for a specific prefix and mode",
+ complete = function(arglead, cmdline)
+ local args = vim.split(cmdline, " ")
+ if #args <= 2 then -- Completing prefix
+ -- You can use a lot more, but this is just examples of what you can do
+ return {
+ "",
+ "x",
+ "x",
+ "",
+ "x",
+ -- Add more common prefixes here
+ }
+ else -- Completing mode
+ return { "n", "v", "x", "s", "o", "i", "t", "c" }
+ end
+ end,
+ })
+ end
+end
+
+_G.KeyAnalyzer = KeyAnalyzer
+
+return _G.KeyAnalyzer
diff --git a/lua/key-analyzer/main.lua b/lua/key-analyzer/main.lua
new file mode 100644
index 0000000..176fb11
--- /dev/null
+++ b/lua/key-analyzer/main.lua
@@ -0,0 +1,254 @@
+local log = require("key-analyzer.util.log")
+
+-- internal methods
+local main = {}
+
+local current_maps = {}
+
+-- QWERTY keyboard layout representation
+local KEYBOARD_LAYOUT = {
+ { "1", "2", "3", "4", "5", "6", "7", "8", "9", "0", "-", "=" },
+ { "q", "w", "e", "r", "t", "y", "u", "i", "o", "p", "[", "]" },
+ { "a", "s", "d", "f", "g", "h", "j", "k", "l", ";", "'" },
+ { "z", "x", "c", "v", "b", "n", "m", ",", ".", "/" },
+}
+
+-- Row offsets for realistic keyboard layout
+local ROW_OFFSETS = {
+ 0,
+ 1,
+ 2,
+ 3,
+}
+
+-- Get all keymaps for a specific mode and prefix
+local function get_modified_maps(mode, prefix)
+ local maps = {}
+ local keymap_list = vim.api.nvim_get_keymap(mode)
+
+ -- Convert leader to actual key if needed
+ local search_prefix = prefix
+ if prefix:match("^") then
+ search_prefix = vim.g.mapleader .. prefix:sub(9)
+ end
+
+ -- Escape special characters for pattern matching
+ local pattern_prefix = vim.pesc(search_prefix)
+
+ print(vim.inspect(pattern_prefix))
+ for _, keymap in ipairs(keymap_list) do
+ local lhs = keymap.lhs
+ -- Check if the mapping starts with our prefix
+ if lhs:match("^" .. pattern_prefix .. "(%w)") then
+ local key = lhs:match("^" .. pattern_prefix .. "(%w)")
+ maps[key:lower()] = keymap.desc
+ or keymap.rhs
+ or "[" .. (keymap.callback and "Lua" or "Unknown") .. "]"
+ end
+
+ -- Also check literal leader key if prefix contains
+ if
+ prefix:match("^")
+ and lhs:match("^" .. pattern_prefix:sub(#vim.g.mapleader + 1) .. "(%w)")
+ then
+ local key = lhs:match("^" .. pattern_prefix:sub(#vim.g.mapleader + 1) .. "(%w)")
+ maps[key:lower()] = keymap.desc
+ or keymap.rhs
+ or "[" .. (keymap.callback and "Lua" or "Unknown") .. "]"
+ end
+ end
+
+ return maps
+end
+
+-- Create a visual representation of the keyboard
+local function create_keyboard_visual(maps, mode, modifier)
+ local lines = {}
+ local highlights = {}
+ local config_highlights = _G.KeyAnalyzer.config.highlights
+
+ if config_highlights.define_default_highlights then
+ -- Create highlight groups for mapped and unmapped keys
+ vim.cmd([[highlight KeyAnalyzerBracketUsed guifg=#aadd00 guibg=#333333 gui=bold]]) -- Mapped brackets (green)
+ vim.cmd([[highlight KeyAnalyzerLetterUsed guifg=#ffff00 guibg=#333333 gui=bold]]) -- Mapped letter (yellow)
+ vim.cmd([[highlight KeyAnalyzerBracketUnused guifg=#444444 gui=none]]) -- Unmapped brackets (dark gray)
+ vim.cmd([[highlight KeyAnalyzerLetterUnused guifg=#888888 gui=none]]) -- Unmapped letter (light gray)
+ vim.cmd([[highlight KeyAnalyzerPromo guifg=#444444 gui=none]]) -- Unmapped brackets (dark gray)
+ end
+
+ -- Add mode and modifier info line
+ local mode_info = {
+ n = "Normal",
+ v = "Visual",
+ x = "Visual Block",
+ s = "Select",
+ o = "Operator-pending",
+ i = "Insert",
+ t = "Terminal",
+ c = "Command",
+ }
+
+ for row_idx, row in ipairs(KEYBOARD_LAYOUT) do
+ local line = string.rep(" ", math.floor(ROW_OFFSETS[row_idx])) -- Apply offset
+ local line_start = #lines + 1
+
+ for col_idx, key in ipairs(row) do
+ local mapping = maps[key]
+ local pos = #line + 1
+ if mapping then
+ -- Highlight positions for mapped key (brackets and letter)
+ table.insert(
+ highlights,
+ { group = config_highlights.bracket_used, pos = { line_start, pos, 1 } }
+ ) -- Left bracket
+ table.insert(
+ highlights,
+ { group = config_highlights.letter_used, pos = { line_start, pos + 1, 1 } }
+ ) -- Letter
+ table.insert(
+ highlights,
+ { group = config_highlights.bracket_used, pos = { line_start, pos + 2, 1 } }
+ ) -- Right bracket
+ else
+ -- Highlight positions for unmapped key (brackets and letter)
+ table.insert(
+ highlights,
+ { group = config_highlights.bracket_unused, pos = { line_start, pos, 1 } }
+ ) -- Left bracket
+ table.insert(
+ highlights,
+ { group = config_highlights.letter_unused, pos = { line_start, pos + 1, 1 } }
+ ) -- Letter
+ table.insert(
+ highlights,
+ { group = config_highlights.bracket_unused, pos = { line_start, pos + 2, 1 } }
+ ) -- Right bracket
+ end
+ line = line .. "[" .. key .. "]"
+ end
+
+ table.insert(lines, line)
+ end
+
+ -- Add mode info after keyboard layout
+ table.insert(lines, "") -- Empty line
+ table.insert(lines, string.format("Mode: %s", mode_info[mode] or mode:upper()))
+ table.insert(lines, string.format("Key: %s", modifier == "leader" and "" or modifier))
+ table.insert(lines, "") -- Empty line
+
+ -- Shameless plug :(
+ table.insert(lines, "For more vim: https://x.com/OtivDev")
+ table.insert(highlights, { group = config_highlights.promo_highlight, pos = { 9, 0, 50 } })
+
+ table.insert(lines, "") -- Empty line
+
+ return lines, highlights
+end
+
+-- Get key at cursor position
+local function get_key_at_cursor()
+ local cursor = vim.api.nvim_win_get_cursor(0)
+ local line = cursor[1]
+ local col = cursor[2] + 1 -- Convert to 1-based index
+
+ -- Only check keyboard layout lines (1-4)
+ if line < 1 or line > 4 then
+ return nil
+ end
+
+ -- Calculate offset for this row
+ local offset = ROW_OFFSETS[line]
+
+ -- Adjust column for offset
+ col = col - offset
+
+ -- Each key takes 3 positions [k]
+ local key_idx = math.floor((col - 1) / 3)
+
+ -- Check if we're within a key bracket
+ if key_idx >= 0 and key_idx < #KEYBOARD_LAYOUT[line] then
+ return KEYBOARD_LAYOUT[line][key_idx + 1]
+ end
+
+ return nil
+end
+
+-- Display the keyboard map in a floating window
+local function show_in_float(lines, highlights, maps)
+ local buf = vim.api.nvim_create_buf(false, true)
+ local width = 37 -- Increased to accommodate brackets and extra keys
+ local height = #lines + 2
+
+ -- Configure buffer
+ vim.api.nvim_buf_set_lines(buf, 0, -1, false, lines)
+ -- vim.api.nvim_buf_set_option(buf, "modifiable", false)
+ vim.api.nvim_buf_set_option(buf, "bufhidden", "wipe")
+
+ -- Create window first so we can apply highlights
+
+ -- Calculate centered position
+ local ui = vim.api.nvim_list_uis()[1]
+ local win_opts = {
+ relative = "editor",
+ width = width,
+ height = height,
+ col = (ui.width - width) / 2,
+ row = (ui.height - height) / 2,
+ style = "minimal",
+ border = "rounded",
+ }
+
+ -- Create and configure window
+ local win = vim.api.nvim_open_win(buf, true, win_opts)
+ vim.api.nvim_win_set_option(win, "winblend", 0)
+
+ vim.o.wrap = true
+
+ -- Apply highlights
+ for _, hl in ipairs(highlights) do
+ vim.fn.matchaddpos(hl.group, { hl.pos })
+ end
+
+ -- Add empty line for mapping display
+ table.insert(lines, "")
+ table.insert(lines, "Hover over a key to see its mapping")
+
+ -- Close on q or
+ vim.keymap.set("n", "q", ":close", { buffer = buf, silent = true })
+ vim.keymap.set("n", "", ":close", { buffer = buf, silent = true })
+
+ -- Set up cursor moved autocmd
+ vim.api.nvim_create_autocmd("CursorMoved", {
+ buffer = buf,
+ callback = function()
+ local key = get_key_at_cursor()
+ if key then
+ local mapping = maps[key]
+ local msg = mapping and ("Mapping: " .. key .. " -> " .. mapping)
+ or "No mapping for key: " .. key
+ vim.api.nvim_buf_set_lines(buf, -2, -1, false, { msg })
+ else
+ vim.api.nvim_buf_set_lines(
+ buf,
+ -2,
+ -1,
+ false,
+ { "Hover over a key to see its mapping" }
+ )
+ end
+ end,
+ })
+end
+
+-- Toggle the plugin by calling the `enable`/`disable` methods respectively.
+--
+---@param mode string: internal identifier for logging purposes.
+---@param prefix string: internal identifier for logging purposes.
+---@private
+function main.show_keyboard_map(mode, prefix)
+ current_maps = get_modified_maps(mode, prefix)
+ local visual, highlights = create_keyboard_visual(current_maps, mode, prefix)
+ show_in_float(visual, highlights, current_maps)
+end
+
+return main
diff --git a/lua/key-analyzer/util/log.lua b/lua/key-analyzer/util/log.lua
new file mode 100644
index 0000000..2437e9f
--- /dev/null
+++ b/lua/key-analyzer/util/log.lua
@@ -0,0 +1,87 @@
+local log = {}
+
+local longest_scope = 15
+
+--- prints only if debug is true.
+---
+---@param scope string: the scope from where this function is called.
+---@param str string: the formatted string.
+---@param ... any: the arguments of the formatted string.
+---@private
+function log.debug(scope, str, ...)
+ return log.notify(scope, vim.log.levels.DEBUG, false, str, ...)
+end
+
+--- prints only if debug is true.
+---
+---@param scope string: the scope from where this function is called.
+---@param level string: the log level of vim.notify.
+---@param verbose boolean: when false, only prints when config.debug is true.
+---@param str string: the formatted string.
+---@param ... any: the arguments of the formatted string.
+---@private
+function log.notify(scope, level, verbose, str, ...)
+ if not verbose and _G.KeyAnalyzer.config ~= nil and not _G.KeyAnalyzer.config.debug then
+ return
+ end
+
+ if string.len(scope) > longest_scope then
+ longest_scope = string.len(scope)
+ end
+
+ for i = longest_scope, string.len(scope), -1 do
+ if i < string.len(scope) then
+ scope = string.format("%s ", scope)
+ else
+ scope = string.format("%s", scope)
+ end
+ end
+
+ vim.notify(
+ string.format("[key-analyzer.nvim@%s] %s", scope, string.format(str, ...)),
+ level,
+ { title = "key-analyzer.nvim" }
+ )
+end
+
+--- analyzes the user provided `setup` parameters and sends a message if they use a deprecated option, then gives the new option to use.
+---
+---@param options table: the options provided by the user.
+---@private
+function log.warn_deprecation(options)
+ local uses_deprecated_option = false
+ local notice = "is now deprecated, use `%s` instead."
+ local root_deprecated = {
+ foo = "bar",
+ bar = "baz",
+ }
+
+ for name, warning in pairs(root_deprecated) do
+ if options[name] ~= nil then
+ uses_deprecated_option = true
+ log.notify(
+ "deprecated_options",
+ vim.log.levels.WARN,
+ true,
+ string.format("`%s` %s", name, string.format(notice, warning))
+ )
+ end
+ end
+
+ if uses_deprecated_option then
+ log.notify(
+ "deprecated_options",
+ vim.log.levels.WARN,
+ true,
+ "sorry to bother you with the breaking changes :("
+ )
+ log.notify(
+ "deprecated_options",
+ vim.log.levels.WARN,
+ true,
+ "use `:h KeyAnalyzer.options` to read more."
+ )
+ end
+end
+
+return log
diff --git a/plugin/key-analyzer.lua b/plugin/key-analyzer.lua
new file mode 100644
index 0000000..3a41f83
--- /dev/null
+++ b/plugin/key-analyzer.lua
@@ -0,0 +1,6 @@
+-- You can use this loaded variable to enable conditional parts of your plugin.
+if _G.KeyAnalyzerLoaded then
+ return
+end
+
+_G.KeyAnalyzerLoaded = true
diff --git a/scripts/minimal_init.lua b/scripts/minimal_init.lua
new file mode 100644
index 0000000..362995b
--- /dev/null
+++ b/scripts/minimal_init.lua
@@ -0,0 +1,15 @@
+-- Add current directory to 'runtimepath' to be able to use 'lua' files
+vim.cmd([[let &rtp.=','.getcwd()]])
+
+-- Set up 'mini.test' and 'mini.doc' only when calling headless Neovim (like with `make test` or `make documentation`)
+if #vim.api.nvim_list_uis() == 0 then
+ -- Add 'mini.nvim' to 'runtimepath' to be able to use 'mini.test'
+ -- Assumed that 'mini.nvim' is stored in 'deps/mini.nvim'
+ vim.cmd("set rtp+=deps/mini.nvim")
+
+ -- Set up 'mini.test'
+ require("mini.test").setup()
+
+ -- Set up 'mini.doc'
+ require("mini.doc").setup()
+end
diff --git a/scripts/setup.sh b/scripts/setup.sh
new file mode 100755
index 0000000..2695c62
--- /dev/null
+++ b/scripts/setup.sh
@@ -0,0 +1,79 @@
+#!/bin/bash
+
+USAGE="\033[0;37m[INFO] - usage: USERNAME=my-github-username PLUGIN_NAME=my-awesome-plugin REPOSITORY_NAME=my-awesome-plugin.nvim make setup\n\033[0m"
+
+echo -e $USAGE
+
+if [[ -z "$USERNAME" ]]; then
+ echo -e "\t> No USERNAME provided, what's your GitHub/GitLab username?"
+ read USERNAME
+fi
+
+if [[ -z "$REPOSITORY_NAME" ]]; then
+ REPOSITORY_NAME=$(basename -s .git `git config --get remote.origin.url`)
+
+ read -rp $'\t> No REPOSITORY_NAME provided, is \033[1;32m'"$REPOSITORY_NAME"$'\033[0m good? [Y/n]\n' yn
+ case $yn in
+ [Yy]* );;
+ [Nn]* )
+ echo -e "\t> Enter your repository name"
+ read REPOSITORY_NAME
+ ;;
+ * )
+ echo -e $USAGE
+ exit 1;;
+ esac
+fi
+
+if [[ -z "$PLUGIN_NAME" ]]; then
+ DEFAULT_REPOSITORY_NAME=$(echo "$REPOSITORY_NAME" | sed -e "s/\.nvim//")
+ read -rp $'\t> No PLUGIN_NAME provided, defaulting to \033[1;32m'"$DEFAULT_REPOSITORY_NAME"$'\033[0m, continue? [Y/n]\n' yn
+ case $yn in
+ [Yy]* )
+ PLUGIN_NAME=$DEFAULT_REPOSITORY_NAME
+ ;;
+ [Nn]* )
+ echo -e "\t> Enter your plugin name"
+ read PLUGIN_NAME
+ ;;
+ * )
+ echo -e $USAGE
+ exit 1;;
+ esac
+fi
+
+echo -e "Username: \033[1;32m$USERNAME\033[0m\nRepository: \033[1;32m$REPOSITORY_NAME\033[0m\nPlugin: \033[1;32m$PLUGIN_NAME\033[0m\n\n\tRenaming placeholder files..."
+
+rm -rf doc
+mv plugin/your-plugin-name.lua plugin/$PLUGIN_NAME.lua
+mv lua/your-plugin-name lua/$PLUGIN_NAME
+mv README_TEMPLATE.md README.md
+
+echo -e "\tReplacing placeholder names..."
+
+PASCAL_CASE_PLUGIN_NAME=$(echo "$PLUGIN_NAME" | perl -pe 's/(^|-)./uc($&)/ge;s/-//g')
+
+grep -rl "YourPluginName" .github/ plugin/ tests/ lua/ | xargs sed -i "" -e "s/YourPluginName/$PASCAL_CASE_PLUGIN_NAME/g"
+grep -rl "your-plugin-name" README.md .github/ plugin/ tests/ lua/ | xargs sed -i "" -e "s/your-plugin-name/$PLUGIN_NAME/g"
+grep -rl "YOUR_GITHUB_USERNAME" README.md .github/ | xargs sed -i "" -e "s/YOUR_GITHUB_USERNAME/$USERNAME/g"
+grep -rl "YOUR_REPOSITORY_NAME" README.md .github/ | xargs sed -i "" -e "s/YOUR_REPOSITORY_NAME/$REPOSITORY_NAME/g"
+
+echo -e "\n\033[1;32mOK.\033[0m"
+
+echo -e "\tFetching dependencies (tests and documentation generator)..."
+
+make deps
+
+echo -e "\n\033[1;32mOK.\033[0m"
+
+echo -e "\tGenerating docs..."
+
+make documentation
+
+echo -e "\n\033[1;32mOK.\033[0m"
+
+echo -e "\tRunning tests..."
+
+make test
+
+echo -e "\n\033[1;32mOK.\033[0m"
diff --git a/stylua.toml b/stylua.toml
new file mode 100644
index 0000000..6376ab8
--- /dev/null
+++ b/stylua.toml
@@ -0,0 +1,5 @@
+indent_type = "Spaces"
+indent_width = 4
+column_width = 100
+quote_style = "AutoPreferDouble"
+no_call_parentheses = false
diff --git a/tests/helpers.lua b/tests/helpers.lua
new file mode 100644
index 0000000..13fa2be
--- /dev/null
+++ b/tests/helpers.lua
@@ -0,0 +1,193 @@
+-- imported from https://github.com/echasnovski/mini.nvim
+local Helpers = {}
+
+-- Add extra expectations
+Helpers.expect = vim.deepcopy(MiniTest.expect)
+
+local function error_message(str, pattern)
+ return string.format("Pattern: %s\nObserved string: %s", vim.inspect(pattern), str)
+end
+
+Helpers.expect.buf_width = MiniTest.new_expectation(
+ "variable in child process matches",
+ function(child, field, value)
+ return Helpers.expect.equality(
+ child.lua_get("vim.api.nvim_win_get_width(_G.KeyAnalyzer.state." .. field .. ")"),
+ value
+ )
+ end,
+ error_message
+)
+
+Helpers.expect.global = MiniTest.new_expectation(
+ "variable in child process matches",
+ function(child, field, value)
+ return Helpers.expect.equality(child.lua_get(field), value)
+ end,
+ error_message
+)
+
+Helpers.expect.global_type = MiniTest.new_expectation(
+ "variable type in child process matches",
+ function(child, field, value)
+ return Helpers.expect.global(child, "type(" .. field .. ")", value)
+ end,
+ error_message
+)
+
+Helpers.expect.config = MiniTest.new_expectation(
+ "config option matches",
+ function(child, field, value)
+ if field == "" then
+ return Helpers.expect.global(child, "_G.KeyAnalyzer.config" .. field, value)
+ else
+ return Helpers.expect.global(child, "_G.KeyAnalyzer.config." .. field, value)
+ end
+ end,
+ error_message
+)
+
+Helpers.expect.config_type = MiniTest.new_expectation(
+ "config option type matches",
+ function(child, field, value)
+ return Helpers.expect.global(child, "type(_G.KeyAnalyzer.config." .. field .. ")", value)
+ end,
+ error_message
+)
+
+Helpers.expect.state = MiniTest.new_expectation("state matches", function(child, field, value)
+ return Helpers.expect.global(child, "_G.KeyAnalyzer.state." .. field, value)
+end, error_message)
+
+Helpers.expect.state_type = MiniTest.new_expectation(
+ "state type matches",
+ function(child, field, value)
+ return Helpers.expect.global(child, "type(_G.KeyAnalyzer.state." .. field .. ")", value)
+ end,
+ error_message
+)
+
+Helpers.expect.match = MiniTest.new_expectation("string matching", function(str, pattern)
+ return str:find(pattern) ~= nil
+end, error_message)
+
+Helpers.expect.no_match = MiniTest.new_expectation("no string matching", function(str, pattern)
+ return str:find(pattern) == nil
+end, error_message)
+
+-- Monkey-patch `MiniTest.new_child_neovim` with helpful wrappers
+Helpers.new_child_neovim = function()
+ local child = MiniTest.new_child_neovim()
+
+ local prevent_hanging = function(method)
+ if not child.is_blocked() then
+ return
+ end
+
+ local msg =
+ string.format("Can not use `child.%s` because child process is blocked.", method)
+ error(msg)
+ end
+
+ child.wait = function(ms)
+ child.loop.sleep(ms or 10)
+ end
+
+ child.nnp = function()
+ child.cmd("KeyAnalyzer")
+ child.wait()
+ end
+
+ child.get_wins_in_tab = function(tab)
+ tab = tab or "_G.KeyAnalyzer.state.active_tab"
+
+ return child.lua_get("vim.api.nvim_tabpage_list_wins(" .. tab .. ")")
+ end
+
+ child.list_buffers = function()
+ return child.lua_get("vim.api.nvim_list_bufs()")
+ end
+
+ child.setup = function()
+ child.restart({ "-u", "scripts/minimal_init.lua" })
+
+ -- Change initial buffer to be readonly. This not only increases execution
+ -- speed, but more closely resembles manually opened Neovim.
+ child.bo.readonly = false
+ end
+
+ child.get_current_win = function()
+ return child.lua_get("vim.api.nvim_get_current_win()")
+ end
+
+ child.set_lines = function(arr, start, finish)
+ prevent_hanging("set_lines")
+
+ if type(arr) == "string" then
+ arr = vim.split(arr, "\n")
+ end
+
+ child.api.nvim_buf_set_lines(0, start or 0, finish or -1, false, arr)
+ end
+
+ child.get_lines = function(start, finish)
+ prevent_hanging("get_lines")
+
+ return child.api.nvim_buf_get_lines(0, start or 0, finish or -1, false)
+ end
+
+ child.set_cursor = function(line, column, win_id)
+ prevent_hanging("set_cursor")
+
+ child.api.nvim_win_set_cursor(win_id or 0, { line, column })
+ end
+
+ child.get_cursor = function(win_id)
+ prevent_hanging("get_cursor")
+
+ return child.api.nvim_win_get_cursor(win_id or 0)
+ end
+
+ child.set_size = function(lines, columns)
+ prevent_hanging("set_size")
+
+ if type(lines) == "number" then
+ child.o.lines = lines
+ end
+
+ if type(columns) == "number" then
+ child.o.columns = columns
+ end
+ end
+
+ child.get_size = function()
+ prevent_hanging("get_size")
+
+ return { child.o.lines, child.o.columns }
+ end
+
+ --- Assert visual marks
+ ---
+ --- Useful to validate visual selection
+ ---
+ ---@param first number|table Table with start position or number to check linewise.
+ ---@param last number|table Table with finish position or number to check linewise.
+ ---@private
+ child.expect_visual_marks = function(first, last)
+ child.ensure_normal_mode()
+
+ first = type(first) == "number" and { first, 0 } or first
+ last = type(last) == "number" and { last, 2147483647 } or last
+
+ MiniTest.expect.equality(child.api.nvim_buf_get_mark(0, "<"), first)
+ MiniTest.expect.equality(child.api.nvim_buf_get_mark(0, ">"), last)
+ end
+
+ child.expect_screenshot = function(opts, path, screenshot_opts)
+ MiniTest.expect.reference_screenshot(child.get_screenshot(screenshot_opts), path, opts)
+ end
+
+ return child
+end
+
+return Helpers
diff --git a/tests/test_API.lua b/tests/test_API.lua
new file mode 100644
index 0000000..632c54b
--- /dev/null
+++ b/tests/test_API.lua
@@ -0,0 +1,52 @@
+local Helpers = dofile("tests/helpers.lua")
+
+-- See https://github.com/echasnovski/mini.nvim/blob/main/lua/mini/test.lua for more documentation
+
+local child = Helpers.new_child_neovim()
+
+local T = MiniTest.new_set({
+ hooks = {
+ -- This will be executed before every (even nested) case
+ pre_case = function()
+ -- Restart child process with custom 'init.lua' script
+ child.restart({ "-u", "scripts/minimal_init.lua" })
+ end,
+ -- This will be executed one after all tests from this set are finished
+ post_once = child.stop,
+ },
+})
+
+-- Tests related to the `setup` method.
+T["setup()"] = MiniTest.new_set()
+
+T["setup()"]["sets exposed methods and default options value"] = function()
+ child.lua([[require('key-analyzer').setup()]])
+
+ -- global object that holds your plugin information
+ Helpers.expect.global_type(child, "_G.KeyAnalyzer", "table")
+
+ -- public methods
+ Helpers.expect.global_type(child, "_G.KeyAnalyzer.toggle", "function")
+ Helpers.expect.global_type(child, "_G.KeyAnalyzer.disable", "function")
+ Helpers.expect.global_type(child, "_G.KeyAnalyzer.enable", "function")
+
+ -- config
+ Helpers.expect.global_type(child, "_G.KeyAnalyzer.config", "table")
+
+ -- assert the value, and the type
+ Helpers.expect.config(child, "debug", false)
+ Helpers.expect.config_type(child, "debug", "boolean")
+end
+
+T["setup()"]["overrides default values"] = function()
+ child.lua([[require('key-analyzer').setup({
+ -- write all the options with a value different than the default ones
+ debug = true,
+ })]])
+
+ -- assert the value, and the type
+ Helpers.expect.config(child, "debug", true)
+ Helpers.expect.config_type(child, "debug", "boolean")
+end
+
+return T