├── .busted ├── .github ├── ISSUE_TEMPLATE │ ├── ask_a_question.yml │ ├── bug_report.yml │ └── feature_request.yml └── workflows │ ├── .luarc.json │ ├── checkhealth.yml │ ├── commitlint.yml │ ├── documentation.yml │ ├── llscheck.yml │ ├── luacheck.yml │ ├── luacov.yml │ ├── news.yml │ ├── release-github.yml │ ├── release-luarocks.yml │ ├── stylua.yml │ ├── test.yml │ └── urlchecker.yml ├── .gitignore ├── .luacheckrc ├── .luacov ├── .luarc.json ├── .stylua.toml ├── CHANGELOG.md ├── LICENSE ├── Makefile ├── README.md ├── doc ├── news.txt ├── plugin-template.txt ├── plugin_template_api.txt ├── plugin_template_types.txt └── tags ├── lua ├── lualine │ └── components │ │ └── plugin_template.lua ├── plugin_template │ ├── _commands │ │ ├── arbitrary_thing │ │ │ ├── parser.lua │ │ │ └── runner.lua │ │ ├── copy_logs │ │ │ ├── parser.lua │ │ │ └── runner.lua │ │ ├── goodnight_moon │ │ │ ├── count_sheep.lua │ │ │ ├── parser.lua │ │ │ ├── read.lua │ │ │ └── sleep.lua │ │ └── hello_world │ │ │ ├── parser.lua │ │ │ └── say │ │ │ ├── constant.lua │ │ │ └── runner.lua │ ├── _core │ │ ├── configuration.lua │ │ ├── tabler.lua │ │ └── texter.lua │ ├── health.lua │ ├── init.lua │ └── types.lua └── telescope │ └── _extensions │ └── plugin_template │ ├── init.lua │ └── runner.lua ├── markdown └── manual │ ├── .gitkeep │ └── docs │ └── index.md ├── plugin └── plugin_template.lua ├── renovate.json ├── scripts ├── luacov.lua └── make_api_documentation │ ├── main.lua │ └── minimal_init.lua ├── spec ├── minimal_init.lua ├── plugin_template │ ├── configuration_spec.lua │ ├── configuration_tools_lualine_spec.lua │ ├── configuration_tools_telescope_spec.lua │ ├── lualine_spec.lua │ ├── plugin_template_spec.lua │ └── telescope_spec.lua └── test_utilities │ ├── mock_test.lua │ └── mock_vim.lua └── template.rockspec /.busted: -------------------------------------------------------------------------------- 1 | return { 2 | _all = { 3 | coverage = false, 4 | lpath = "lua/?.lua;lua/?/init.lua;spec/?.lua", 5 | lua = "nvim -u NONE -U NONE -N -i NONE -l", 6 | }, 7 | default = { 8 | helper = "./spec/minimal_init.lua", 9 | verbose = true, 10 | }, 11 | tests = { 12 | verbose = true, 13 | }, 14 | } 15 | -------------------------------------------------------------------------------- /.github/ISSUE_TEMPLATE/ask_a_question.yml: -------------------------------------------------------------------------------- 1 | blank_issues_enabled: false 2 | contact_links: 3 | - name: Ask a question 4 | url: https://github.com/ColinKennedy/nvim-best-practices-plugin-template/discussions 5 | about: Use Github discussions instead 6 | -------------------------------------------------------------------------------- /.github/ISSUE_TEMPLATE/bug_report.yml: -------------------------------------------------------------------------------- 1 | name: Bug Report 2 | description: File a bug/issue 3 | title: "bug: " 4 | labels: [bug] 5 | body: 6 | - type: markdown 7 | attributes: 8 | value: | 9 | **Before** reporting an issue, make sure to read the [documentation](https://github.com/ColinKennedy/nvim-best-practices-plugin-template) 10 | and search [existing issues](https://github.com/ColinKennedy/nvim-best-practices-plugin-template/issues) (even the [closed issues](https://github.com/ColinKennedy/nvim-best-practices-plugin-template/issues?q=is%3Aissue+is%3Aclosed)) 11 | 12 | Usage questions such as ***"How do I...?"*** belong in [Discussions](https://github.com/ColinKennedy/nvim-best-practices-plugin-template/discussions) and will be closed. 13 | - type: checkboxes 14 | attributes: 15 | label: Did you read the documentation and check existing issues? 16 | description: Make sure you checked and all of the below before submitting an issue 17 | options: 18 | - label: I have read all the [`:help plugin-template`](https://github.com/ColinKennedy/nvim-best-practices-plugin-template/blob/main/doc/my-template-docs.txt) documentation 19 | required: true 20 | - label: I have updated the plugin to the latest version before submitting this issue 21 | required: true 22 | - label: I have searched the [existing issues](https://github.com/ColinKennedy/nvim-best-practices-plugin-template/issues) and [closed issues](https://github.com/ColinKennedy/nvim-best-practices-plugin-template/issues?q=is%3Aissue+is%3Aclosed) issues 23 | required: true 24 | - type: input 25 | attributes: 26 | label: "Neovim version (nvim -v)" 27 | placeholder: "0.8.0 commit db1b0ee3b30f" 28 | validations: 29 | required: true 30 | - type: input 31 | attributes: 32 | label: "Operating system/version" 33 | placeholder: "Rocky 9" 34 | validations: 35 | required: true 36 | - type: textarea 37 | attributes: 38 | label: Describe the bug 39 | description: A clear and concise description of what the bug is. Please include any related errors you see in Neovim. 40 | validations: 41 | required: true 42 | - type: textarea 43 | attributes: 44 | label: Steps To Reproduce 45 | description: Steps to reproduce the behavior. 46 | placeholder: | 47 | 1. 48 | 2. 49 | 3. 50 | validations: 51 | required: true 52 | - type: textarea 53 | attributes: 54 | label: Expected Behavior 55 | description: A concise description of what you expected to happen. 56 | validations: 57 | required: true 58 | - type: textarea 59 | attributes: 60 | label: Health 61 | description: Attach the output of `:checkhealth plugin_template` here 62 | render: log 63 | - type: textarea 64 | attributes: 65 | label: Log 66 | description: Please enable logging with `vim.g.plugin_template_configuration = {logging = {level = "debug", use_file = true}}` and attach the contents of `~/.local/share/nvim` here or call `:PluginTemplate copy-logs` 67 | render: log 68 | - type: textarea 69 | attributes: 70 | label: Repro 71 | description: Minimal `init.lua` to reproduce this issue. Save as `repro.lua` and run with `nvim -u repro.lua` 72 | value: | 73 | vim.env.LAZY_STDPATH = ".repro" 74 | load(vim.fn.system("curl -s https://raw.githubusercontent.com/folke/lazy.nvim/main/bootstrap.lua"))() 75 | 76 | require("lazy.minit").repro({ 77 | spec = { 78 | { 79 | -- Add anything you need here (configuration, other plugins, etc) 80 | "ColinKennedy/nvim-best-practices-plugin-template", 81 | }, 82 | }) 83 | render: lua 84 | validations: 85 | required: false 86 | -------------------------------------------------------------------------------- /.github/ISSUE_TEMPLATE/feature_request.yml: -------------------------------------------------------------------------------- 1 | name: Feature Request 2 | description: Suggest a new feature 3 | title: "feature: " 4 | labels: [enhancement] 5 | body: 6 | - type: markdown 7 | attributes: 8 | value: | 9 | Thank you for suggestion! Depending on your request, the issue may be reposted to https://github.com/nvim-neorocks/nvim-best-practices (which this plugin takes great inspiration). But please feel free to ask here first! 10 | - type: checkboxes 11 | attributes: 12 | label: Did you check the documentation? 13 | description: Make sure you read all the docs before submitting a feature request 14 | options: 15 | - label: I have read all the docs 16 | required: true 17 | - type: textarea 18 | validations: 19 | required: true 20 | attributes: 21 | label: Is your feature request related to a problem? Please describe. 22 | description: A clear and concise description of what the problem is. Ex. Wouldn't it be cool if [...] Ex. I'm always frustrated when [...] 23 | - type: textarea 24 | validations: 25 | required: true 26 | attributes: 27 | label: Describe the solution you'd like 28 | description: A clear and concise description of what you want to happen. 29 | - type: textarea 30 | validations: 31 | required: true 32 | attributes: 33 | label: Describe alternatives you've considered 34 | description: A clear and concise description of any alternative solutions or features you've considered. 35 | - type: textarea 36 | validations: 37 | required: false 38 | attributes: 39 | label: Additional context 40 | description: Add any other context or screenshots about the feature request here. 41 | -------------------------------------------------------------------------------- /.github/workflows/.luarc.json: -------------------------------------------------------------------------------- 1 | { 2 | "diagnostics.libraryFiles": "Disable", 3 | "runtime.version": "LuaJIT", 4 | "workspace.checkThirdParty": "Disable", 5 | "workspace.ignoreDir": [".dependencies", ".lua", ".luarocks"], 6 | "workspace.library": [ 7 | "$PWD/.dependencies/busted/library", 8 | "$PWD/.dependencies/luassert/library", 9 | "$PWD/.dependencies/luvit-meta/library", 10 | "$PWD/.dependencies/mega.cmdparse/lua", 11 | "$PWD/.dependencies/mega.logging/lua", 12 | "$VIMRUNTIME/lua" 13 | ] 14 | } 15 | -------------------------------------------------------------------------------- /.github/workflows/checkhealth.yml: -------------------------------------------------------------------------------- 1 | name: Neovim checkhealth 2 | on: 3 | pull_request: 4 | types: [opened, synchronize, reopened, ready_for_review] 5 | branches: 6 | - main 7 | push: 8 | branches: 9 | - main 10 | 11 | jobs: 12 | checkhealth: 13 | strategy: 14 | matrix: 15 | os: [ubuntu-latest, macos-latest, windows-latest] 16 | neovim: [v0.10.0, stable, nightly] 17 | 18 | runs-on: ${{ matrix.os }} 19 | name: "OS: ${{ matrix.os }} - Neovim: ${{ matrix.neovim }}" 20 | 21 | steps: 22 | - name: Checkout code 23 | uses: actions/checkout@v4 24 | 25 | - name: Install Neovim 26 | uses: rhysd/action-setup-vim@v1 27 | with: 28 | neovim: true 29 | version: ${{ matrix.neovim }} 30 | 31 | - name: Run :checkhealth 32 | uses: ColinKennedy/nvim-checkhealth-gh-action@v1.1.1 33 | with: 34 | executable: 'nvim -u "spec/minimal_init.lua"' 35 | checks: "plugin_template vim.*" 36 | -------------------------------------------------------------------------------- /.github/workflows/commitlint.yml: -------------------------------------------------------------------------------- 1 | # Reference: https://commitlint.js.org/guides/ci-setup 2 | name: Commitlint 3 | 4 | on: [pull_request] 5 | 6 | jobs: 7 | commitlint: 8 | runs-on: ubuntu-latest 9 | name: Commitlint 10 | steps: 11 | - name: Run commitlint 12 | uses: opensource-nepal/commitlint@v1 13 | -------------------------------------------------------------------------------- /.github/workflows/documentation.yml: -------------------------------------------------------------------------------- 1 | name: Documentation 2 | 3 | on: 4 | push: 5 | branches: 6 | - main 7 | 8 | jobs: 9 | check_documentation_branch: 10 | name: Check For A Documentation Branch 11 | runs-on: ubuntu-latest 12 | outputs: 13 | exists: ${{ steps.check_branch.outputs.exists }} 14 | steps: 15 | - name: Checkout repository 16 | uses: actions/checkout@v4 17 | 18 | - name: Check If A Valid Documentation Branch Exists 19 | id: check_branch 20 | run: | 21 | if git ls-remote --exit-code origin refs/heads/gh-pages; then 22 | echo "The branch exists" 23 | echo "exists=true" >> $GITHUB_OUTPUT 24 | else 25 | echo "The branch does not exist" 26 | echo "exists=false" >> $GITHUB_OUTPUT 27 | fi 28 | 29 | html_documentation: 30 | needs: check_documentation_branch 31 | if: needs.check_documentation_branch.outputs.exists == 'true' 32 | name: HTML Documentation 33 | runs-on: ubuntu-latest 34 | steps: 35 | - name: Checkout repository 36 | uses: actions/checkout@v4 37 | 38 | - name: Install emmylua_doc_cli From crates.io 39 | uses: baptiste0928/cargo-install@v3 40 | with: 41 | crate: emmylua_doc_cli 42 | 43 | - name: Generate Mkdocs Files With emmylua_doc_cli 44 | run: emmylua_doc_cli --input lua/ --output markdown/generated --mixin markdown/manual 45 | 46 | - name: Deploy To GitHub Pages 47 | uses: mhausenblas/mkdocs-deploy-gh-pages@1.26 48 | env: 49 | GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} 50 | CONFIG_FILE: markdown/generated/mkdocs.yml 51 | 52 | 53 | vim_documentation: 54 | name: Vim Documentation 55 | runs-on: ubuntu-latest 56 | steps: 57 | - name: Checkout repository 58 | uses: actions/checkout@v4 59 | 60 | - name: Install Neovim 61 | uses: rhysd/action-setup-vim@v1 62 | with: 63 | neovim: true 64 | version: stable 65 | 66 | - name: Create API Documentation 67 | run: | 68 | nvim --version 69 | make api-documentation 70 | 71 | - name: Create User Documentation 72 | uses: kdheepak/panvimdoc@main 73 | with: 74 | vimdoc: plugin-template 75 | version: "Neovim >= 0.8.0" 76 | demojify: true 77 | treesitter: true 78 | 79 | - name: Generate Tags 80 | run: | 81 | nvim -c 'helptags doc' -c 'quit' 82 | 83 | - name: Push Changes 84 | uses: stefanzweifel/git-auto-commit-action@v5 85 | with: 86 | commit_message: "docs(vimdoc): Auto-generate user / API documentation + vimtags" 87 | commit_user_name: "github-actions[bot]" 88 | commit_user_email: "github-actions[bot]@users.noreply.github.com" 89 | commit_author: "github-actions[bot] " 90 | -------------------------------------------------------------------------------- /.github/workflows/llscheck.yml: -------------------------------------------------------------------------------- 1 | name: llscheck 2 | 3 | on: 4 | pull_request: 5 | types: [opened, synchronize, reopened, ready_for_review] 6 | branches: 7 | - main 8 | push: 9 | branches: 10 | - main 11 | 12 | jobs: 13 | llscheck: 14 | runs-on: ubuntu-latest 15 | name: llscheck 16 | 17 | steps: 18 | - name: Checkout 19 | uses: actions/checkout@master 20 | 21 | - name: Install Neovim 22 | uses: rhysd/action-setup-vim@v1 23 | # NOTE: We need Neovim installed in order to expose the $VIMRUNTIME 24 | # environment variable 25 | with: 26 | neovim: true 27 | version: stable 28 | 29 | - name: Install A Lua Interpreter 30 | uses: leafo/gh-actions-lua@v11 31 | with: 32 | # Neovim is compiled with LuaJIT so we might as well match. But it 33 | # doesn't look like we can match it exactly. 34 | # 35 | # Reference: 36 | # https://github.com/leafo/gh-actions-lua/issues/49#issuecomment-2295071198 37 | # 38 | luaVersion: "luajit-openresty" 39 | 40 | - name: Install lua-language-server 41 | uses: jdx/mise-action@v2 42 | with: 43 | tool_versions: | 44 | lua-language-server latest 45 | 46 | - name: Install luarocks 47 | uses: leafo/gh-actions-luarocks@v5 48 | 49 | - name: Install llscheck 50 | run: | 51 | luarocks install llscheck 52 | 53 | - name: Print Version 54 | run: | 55 | lua-language-server --version 56 | 57 | - name: Test 58 | run: | 59 | # We use SSH in the `Makefile` but GitHub actions don't allow that. So 60 | # we force git to clone using HTTP instead. 61 | # 62 | export GIT_CONFIG=~/.gitconfig 63 | git config url."https://github.com/".insteadOf git@github.com: 64 | 65 | # Now do the llscheck (and any git clones, as needed) 66 | make llscheck CONFIGURATION=.github/workflows/.luarc.json 67 | -------------------------------------------------------------------------------- /.github/workflows/luacheck.yml: -------------------------------------------------------------------------------- 1 | name: Luacheck 2 | 3 | on: 4 | pull_request: {} 5 | push: 6 | branches: 7 | - main 8 | 9 | jobs: 10 | luacheck: 11 | name: Luacheck 12 | runs-on: ubuntu-24.04 13 | steps: 14 | - uses: actions/checkout@v4 15 | 16 | - name: Prepare 17 | run: | 18 | sudo apt-get update 19 | sudo apt-get install -y luarocks 20 | sudo luarocks install luacheck 21 | 22 | - name: Lint 23 | run: make luacheck 24 | -------------------------------------------------------------------------------- /.github/workflows/luacov.yml: -------------------------------------------------------------------------------- 1 | name: LuaCov 2 | 3 | on: 4 | pull_request: 5 | types: [opened, synchronize, reopened, ready_for_review] 6 | branches: 7 | - main 8 | push: 9 | branches: 10 | - main 11 | 12 | jobs: 13 | # NOTE: The purpose of this test is to make sure that coverage is generatable 14 | # on every OS. Maybe in the future we'll enforce minimum coverage 15 | # requirements. But for now this is a great start. 16 | # 17 | smoke_test: 18 | strategy: 19 | matrix: 20 | os: [ubuntu-latest, macos-latest, windows-latest] 21 | neovim: [stable] 22 | luaVersion: ["luajit-openresty"] 23 | 24 | runs-on: ${{ matrix.os }} 25 | name: "Smoke-test: OS ${{ matrix.os }}" 26 | 27 | steps: 28 | - name: Checkout 29 | uses: actions/checkout@master 30 | 31 | - name: Install Neovim 32 | uses: rhysd/action-setup-vim@v1 33 | with: 34 | neovim: true 35 | version: ${{ matrix.neovim }} 36 | 37 | - name: Setup MSVC 38 | # the 'luarocks/gh-actions-lua' step requires msvc to build PUC-Rio Lua 39 | # versions on Windows (LuaJIT will be build using MinGW/gcc). 40 | if: ${{ matrix.toolchain == 'msvc' }} 41 | uses: ilammy/msvc-dev-cmd@v1 42 | 43 | - name: Install Lua 44 | uses: luarocks/gh-actions-lua@master 45 | with: 46 | luaVersion: "${{ matrix.luaVersion }}" 47 | 48 | - name: Install LuaRocks 49 | uses: luarocks/gh-actions-luarocks@v5 50 | 51 | # TODO: Consider caching this workflow step. It can take ~1 minute to run. 52 | - name: Install Dependencies 53 | run: | 54 | luarocks install busted 55 | luarocks install luacov 56 | luarocks install luacov-multiple 57 | 58 | # NOTE: `make coverage-html` includes SSH-style URLs. This command will 59 | # swap them to HTTP so we don't have to setup SSH agents on this GitHub 60 | # workflow manually. 61 | # 62 | - name: Configure Git To Use HTTPS Instead Of SSH 63 | run: | 64 | git config --global url."https://github.com/".insteadOf "git@github.com:" 65 | 66 | - name: Run LuaCov 67 | run: | 68 | make coverage-html 69 | 70 | - name: Verify luacov_html/index.html Exists 71 | shell: bash 72 | run: | 73 | if [ ! -d "luacov_html" ]; then 74 | echo "Directory 'luacov_html' does not exist" 75 | 76 | exit 1 77 | fi 78 | 79 | if [ ! -f "luacov_html/index.html" ]; then 80 | echo "File 'luacov_html/index.html' does not exist" 81 | 82 | exit 1 83 | fi 84 | 85 | echo "All checks passed." 86 | -------------------------------------------------------------------------------- /.github/workflows/news.yml: -------------------------------------------------------------------------------- 1 | # Reference: https://github.com/neovim/neovim/blob/9762c5e3406cab8152d8dd161c0178965d841676/.github/workflows/news.yml 2 | name: Check news.txt 3 | 4 | on: 5 | pull_request: 6 | types: [opened, synchronize, reopened, ready_for_review, labeled, unlabeled] 7 | branches: 8 | - main 9 | 10 | jobs: 11 | check: 12 | name: Check news.txt 13 | runs-on: ubuntu-latest 14 | if: github.event.pull_request.draft == false && !contains(github.event.pull_request.labels.*.name, 'ci:skip-news') 15 | steps: 16 | - name: Checkout 17 | uses: actions/checkout@v4 18 | with: 19 | fetch-depth: 0 20 | ref: ${{ github.event.pull_request.head.sha }} 21 | - name: Check If news.txt Needs To Be Updated 22 | run: | 23 | for commit in $(git rev-list HEAD~${{ github.event.pull_request.commits }}..HEAD); do 24 | message=$(git log -n1 --pretty=format:%s $commit) 25 | type="$(echo "$message" | sed -E 's|([[:alpha:]]+)(\(.*\))?!?:.*|\1|')" 26 | breaking="$(echo "$message" | sed -E 's|[[:alpha:]]+(\(.*\))?!:.*|breaking-change|')" 27 | if [[ "$type" == "feat" ]] || [[ "$breaking" == "breaking-change" ]]; then 28 | ! git diff HEAD~${{ github.event.pull_request.commits }}..HEAD --quiet doc/news.txt || 29 | { 30 | echo " 31 | Pull request includes a new feature or a breaking change, but 32 | news.txt hasn't been updated yet. This is just a reminder 33 | that news.txt may need to be updated. You can ignore this CI 34 | failure if you think the change won't be of interest to 35 | users." 36 | exit 1 37 | } 38 | fi 39 | done 40 | -------------------------------------------------------------------------------- /.github/workflows/release-github.yml: -------------------------------------------------------------------------------- 1 | # TODO: (you) - To enable this worflow, you must create a "Personal Access Token". 2 | # 3 | # References: 4 | # https://github.com/nvim-neorocks/sample-luarocks-plugin?tab=readme-ov-file#installing-release-please-recommended 5 | # Reference: https://github.com/nvim-neorocks/sample-luarocks-plugin?tab=readme-ov-file#generating-a-pat-personal-access-token 6 | # 7 | name: Release To GitHub 8 | 9 | on: 10 | push: 11 | branches: 12 | - main 13 | workflow_dispatch: 14 | 15 | permissions: 16 | contents: write 17 | pull-requests: write 18 | 19 | jobs: 20 | release: 21 | name: Release To GitHub 22 | runs-on: ubuntu-latest 23 | steps: 24 | - uses: googleapis/release-please-action@v4 25 | with: 26 | token: ${{ secrets.PERSONAL_ACCESS_TOKEN }} 27 | release-type: simple 28 | -------------------------------------------------------------------------------- /.github/workflows/release-luarocks.yml: -------------------------------------------------------------------------------- 1 | # TODO: (you) - To enable this workflow, you need to provide a LUAROCKS_API_KEY 2 | # 3 | # References: 4 | # https://github.com/nvim-neorocks/sample-luarocks-plugin?tab=readme-ov-file#publishing-to-luarocks 5 | # https://github.com/ellisonleao/nvim-plugin-template/blob/922c0d5249076416c5d84e7c0504f1154225a7ab/.github/workflows/release.yml 6 | # 7 | name: Release To LuaRocks 8 | 9 | on: 10 | push: 11 | tags: 12 | - 'v*' 13 | 14 | jobs: 15 | luarocks-upload: 16 | runs-on: ubuntu-24.04 17 | steps: 18 | - uses: actions/checkout@v4 19 | - name: LuaRocks Upload 20 | uses: nvim-neorocks/luarocks-tag-release@v7 21 | env: 22 | LUAROCKS_API_KEY: ${{ secrets.LUAROCKS_API_KEY }} 23 | with: 24 | template: "template.rockspec" 25 | -------------------------------------------------------------------------------- /.github/workflows/stylua.yml: -------------------------------------------------------------------------------- 1 | name: StyLua 2 | 3 | on: 4 | pull_request: 5 | branches: 6 | - main 7 | push: 8 | branches: 9 | - main 10 | 11 | jobs: 12 | stylua: 13 | name: StyLua 14 | runs-on: ubuntu-latest 15 | steps: 16 | - name: Checkout 17 | uses: actions/checkout@v4 18 | 19 | # TODO: Change this once an outcome for this PR is done 20 | # 21 | # Reference: https://github.com/JohnnyMorganz/stylua-action/pull/58 22 | # 23 | - name: Install StyLua 24 | uses: JohnnyMorganz/stylua-action@v4.1.0 25 | with: 26 | version: latest 27 | token: ${{ secrets.GITHUB_TOKEN }} 28 | args: false 29 | 30 | - name: Run StyLua 31 | run: | 32 | make check-stylua 33 | -------------------------------------------------------------------------------- /.github/workflows/test.yml: -------------------------------------------------------------------------------- 1 | name: Test 2 | 3 | on: 4 | pull_request: 5 | types: [opened, synchronize, reopened, ready_for_review] 6 | branches: 7 | - main 8 | push: 9 | branches: 10 | - main 11 | 12 | jobs: 13 | test: 14 | strategy: 15 | matrix: 16 | os: [ubuntu-latest, macos-latest] 17 | neovim: [v0.10.0, v0.11.0, stable, nightly] 18 | luaVersion: ["luajit-openresty"] 19 | include: 20 | - os: "windows-latest" 21 | toolchain: "msvc" 22 | luaVersion: "5.1" 23 | neovim: "v0.10.0" 24 | - os: "windows-latest" 25 | toolchain: "msvc" 26 | luaVersion: "5.1" 27 | neovim: "stable" 28 | - os: "windows-latest" 29 | toolchain: "msvc" 30 | luaVersion: "5.1" 31 | neovim: "nightly" 32 | 33 | runs-on: ${{ matrix.os }} 34 | name: "OS: ${{ matrix.os }} - Neovim: ${{ matrix.neovim }} - Lua: ${{ matrix.luaVersion }}" 35 | 36 | steps: 37 | - name: Checkout 38 | uses: actions/checkout@master 39 | 40 | - name: Install Neovim 41 | uses: rhysd/action-setup-vim@v1 42 | with: 43 | neovim: true 44 | version: ${{ matrix.neovim }} 45 | 46 | - name: Setup MSVC 47 | # the 'luarocks/gh-actions-lua' step requires msvc to build PUC-Rio Lua 48 | # versions on Windows (LuaJIT will be build using MinGW/gcc). 49 | if: ${{ matrix.toolchain == 'msvc' }} 50 | uses: ilammy/msvc-dev-cmd@v1 51 | 52 | - name: Install Lua 53 | uses: luarocks/gh-actions-lua@master 54 | with: 55 | luaVersion: "${{ matrix.luaVersion }}" 56 | 57 | - name: Install LuaRocks 58 | uses: luarocks/gh-actions-luarocks@v5 59 | 60 | # We need this hack until a better solution is available. 61 | # 62 | # Reference: https://github.com/nvim-neorocks/luarocks-tag-release/issues/435 63 | # 64 | - name: Expand The Template Rockspec 65 | uses: ColinKennedy/luarocks-rockspec-expander@v1.0.1 66 | with: 67 | input: template.rockspec 68 | output: nvim-best-practices-plugin-template-scm-1.rockspec 69 | delete_input_after: true 70 | 71 | - name: Build Test Dependencies 72 | run: | 73 | luarocks test nvim-best-practices-plugin-template-scm-1.rockspec --prepare 74 | 75 | - name: Test 76 | run: | 77 | luarocks test --test-type busted 78 | -------------------------------------------------------------------------------- /.github/workflows/urlchecker.yml: -------------------------------------------------------------------------------- 1 | # TODO: (you) Make sure to update your file types below to whatever makes sense for you. 2 | 3 | name: URLChecker 4 | 5 | on: 6 | pull_request: 7 | types: [opened, synchronize, reopened, ready_for_review] 8 | branches: 9 | - main 10 | push: 11 | branches: 12 | - main 13 | 14 | jobs: 15 | urlchecker: 16 | runs-on: ubuntu-latest 17 | 18 | steps: 19 | - name: Checkout 20 | uses: actions/checkout@v4 21 | 22 | - name: Install urlchecker 23 | run: | 24 | python -m pip install urlchecker 25 | 26 | - name: "Directory: doc" 27 | run: | 28 | urlchecker check \ 29 | --branch main \ 30 | --subfolder doc \ 31 | --exclude-patterns "http://0.0.0.0:8000" \ 32 | --no-print \ 33 | --file-types .json,.lua,.md,.txt,.vim,.yml \ 34 | --retry-count 1 \ 35 | --timeout 5 \ 36 | . 37 | 38 | - name: "Directory: lua" 39 | run: | 40 | urlchecker check \ 41 | --branch main \ 42 | --subfolder lua \ 43 | --no-print \ 44 | --file-types .json,.lua,.md,.txt,.vim,.yml \ 45 | --retry-count 1 \ 46 | --timeout 5 \ 47 | . 48 | 49 | - name: "Directory: plugin" 50 | run: | 51 | urlchecker check \ 52 | --branch main \ 53 | --subfolder plugin \ 54 | --no-print \ 55 | --file-types .json,.lua,.md,.txt,.vim,.yml \ 56 | --retry-count 1 \ 57 | --timeout 5 \ 58 | . 59 | 60 | - name: "Directory: scripts" 61 | run: | 62 | urlchecker check \ 63 | --branch main \ 64 | --subfolder scripts \ 65 | --no-print \ 66 | --file-types .json,.lua,.md,.txt,.vim,.yml \ 67 | --retry-count 1 \ 68 | --timeout 5 \ 69 | . 70 | 71 | - name: "Directory: spec" 72 | run: | 73 | urlchecker check \ 74 | --branch main \ 75 | --subfolder scripts \ 76 | --no-print \ 77 | --file-types .json,.lua,.md,.txt,.vim,.yml \ 78 | --retry-count 1 \ 79 | --timeout 5 \ 80 | . 81 | 82 | - name: "File: CHANGELOG.md" 83 | if: ${{ github.event.pull_request.head.ref == 'release-please--branches--main' }} 84 | run: | 85 | urlchecker check \ 86 | --branch main \ 87 | --subfolder . \ 88 | --exclude-patterns "https://github.com/ColinKennedy/nvim-best-practices-plugin-template/compare/v" \ 89 | --files CHANGELOG.md \ 90 | --no-print \ 91 | --file-types .json,.lua,.md,.txt,.vim,.yml \ 92 | --retry-count 1 \ 93 | --timeout 5 \ 94 | . 95 | 96 | - name: "File: CHANGELOG.md" 97 | if: ${{ github.event.pull_request.head.ref != 'release-please--branches--main' }} 98 | run: | 99 | urlchecker check \ 100 | --branch main \ 101 | --subfolder . \ 102 | --files CHANGELOG.md \ 103 | --no-print \ 104 | --retry-count 1 \ 105 | --timeout 5 \ 106 | . 107 | 108 | - name: "File: README.md" 109 | run: | 110 | urlchecker check \ 111 | --branch main \ 112 | --subfolder . \ 113 | --exclude-patterns "http://0.0.0.0:8000" \ 114 | --files README.md \ 115 | --no-print \ 116 | --retry-count 1 \ 117 | --timeout 5 \ 118 | . 119 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | .dependencies/ 2 | -------------------------------------------------------------------------------- /.luacheckrc: -------------------------------------------------------------------------------- 1 | -- Rerun tests only if their modification time changed. 2 | cache = true 3 | 4 | std = luajit 5 | codes = true 6 | 7 | self = false 8 | 9 | -- Reference: https://luacheck.readthedocs.io/en/stable/warnings.html 10 | ignore = { 11 | -- Neovim lua API + luacheck thinks variables like `vim.wo.spell = true` is 12 | -- invalid when it actually is valid. So we have to display rule `W122`. 13 | -- 14 | "122", 15 | } 16 | 17 | -- Global objects defined by the C code 18 | read_globals = { "vim" } 19 | 20 | exclude_files = { } 21 | -------------------------------------------------------------------------------- /.luacov: -------------------------------------------------------------------------------- 1 | return { 2 | include = {"lua"}, 3 | exclude = { 4 | ".luarocks/", 5 | "/tmp", 6 | "/usr", 7 | "spec/", 8 | os.getenv("XDG_CONFIG_HOME"), 9 | }, 10 | tick = true 11 | } 12 | -------------------------------------------------------------------------------- /.luarc.json: -------------------------------------------------------------------------------- 1 | { 2 | "diagnostics.libraryFiles": "Disable", 3 | "runtime.version": "LuaJIT", 4 | "workspace.checkThirdParty": "Disable", 5 | "workspace.ignoreDir": [ 6 | ".dependencies" 7 | ], 8 | "workspace.library": [ 9 | "$PWD/.dependencies/busted/library", 10 | "$PWD/.dependencies/luassert/library", 11 | "$PWD/.dependencies/luvit-meta/library", 12 | "$PWD/.dependencies/mega.cmdparse/lua", 13 | "$PWD/.dependencies/mega.logging/lua", 14 | "$VIMRUNTIME/lua" 15 | ] 16 | } 17 | -------------------------------------------------------------------------------- /.stylua.toml: -------------------------------------------------------------------------------- 1 | indent_type = "Spaces" 2 | no_call_parentheses = false 3 | -------------------------------------------------------------------------------- /CHANGELOG.md: -------------------------------------------------------------------------------- 1 | # Changelog 2 | 3 | ## [1.10.0](https://github.com/ColinKennedy/nvim-best-practices-plugin-template/compare/v1.9.0...v1.10.0) (2025-04-12) 4 | 5 | 6 | ### Features 7 | 8 | * **ci:** added LuaCov support ([0585a10](https://github.com/ColinKennedy/nvim-best-practices-plugin-template/commit/0585a10f66360b2b2f9b2642296fa732c222683f)) 9 | * **ci:** added LuaCov support ([df5ae92](https://github.com/ColinKennedy/nvim-best-practices-plugin-template/commit/df5ae92ac3427d4af899e3a9cb2e561e1eac3fde)) 10 | 11 | ## [1.9.0](https://github.com/ColinKennedy/nvim-best-practices-plugin-template/compare/v1.8.0...v1.9.0) (2025-04-06) 12 | 13 | 14 | ### Features 15 | 16 | * **neovim:** added neovim version v0.11.0 support ([a02c0b3](https://github.com/ColinKennedy/nvim-best-practices-plugin-template/commit/a02c0b303726b5259b3cbc0a2b6acebb98c55c15)) 17 | 18 | ## [1.8.0](https://github.com/ColinKennedy/nvim-best-practices-plugin-template/compare/v1.7.1...v1.8.0) (2025-03-21) 19 | 20 | 21 | ### Features 22 | 23 | * **checkhealth:** added automated :checkhealth CI command ([b0f0ccf](https://github.com/ColinKennedy/nvim-best-practices-plugin-template/commit/b0f0ccfc177cf42cd17faf435b40ca5b39f478f1)) 24 | 25 | ## [1.7.1](https://github.com/ColinKennedy/nvim-best-practices-plugin-template/compare/v1.7.0...v1.7.1) (2025-02-27) 26 | 27 | 28 | ### Bug Fixes 29 | 30 | * **ci:** baptiste0928/cargo-install version key removed ([e9bb024](https://github.com/ColinKennedy/nvim-best-practices-plugin-template/commit/e9bb024fe7f9a067f70a84c6d67ebb567f9a5d83)) 31 | 32 | ## [1.7.0](https://github.com/ColinKennedy/nvim-best-practices-plugin-template/compare/v1.6.0...v1.7.0) (2025-02-06) 33 | 34 | 35 | ### Features 36 | 37 | * **docs:** added automated uploads to gh-pages ([af8b2e5](https://github.com/ColinKennedy/nvim-best-practices-plugin-template/commit/af8b2e5ae9cb0681ca27973787487502156fe2c2)) 38 | 39 | ## [1.6.0](https://github.com/ColinKennedy/nvim-best-practices-plugin-template/compare/v1.5.5...v1.6.0) (2025-01-25) 40 | 41 | 42 | ### Features 43 | 44 | * **os:** added windows support ([7a3c6c7](https://github.com/ColinKennedy/nvim-best-practices-plugin-template/commit/7a3c6c794712883399626d3177b6ce93cf0252a4)) 45 | 46 | ## [1.5.5](https://github.com/ColinKennedy/nvim-best-practices-plugin-template/compare/v1.5.4...v1.5.5) (2025-01-11) 47 | 48 | 49 | ### Bug Fixes 50 | 51 | * **rockspec:** removed lualine because it is still WIP ([121681f](https://github.com/ColinKennedy/nvim-best-practices-plugin-template/commit/121681f8f7ace45e114ea2161175e37c8c8e4877)) 52 | 53 | ## [1.5.4](https://github.com/ColinKennedy/nvim-best-practices-plugin-template/compare/v1.5.3...v1.5.4) (2025-01-11) 54 | 55 | 56 | ### Bug Fixes 57 | 58 | * **rockspec:** added missing lua dependencies ([ac4aea4](https://github.com/ColinKennedy/nvim-best-practices-plugin-template/commit/ac4aea4f468b4912e694eb4fca0305aff026f4e1)) 59 | 60 | ## [1.5.3](https://github.com/ColinKennedy/nvim-best-practices-plugin-template/compare/v1.5.2...v1.5.3) (2025-01-11) 61 | 62 | 63 | ### Bug Fixes 64 | 65 | * **rockspec:** updated package name ([2445735](https://github.com/ColinKennedy/nvim-best-practices-plugin-template/commit/2445735817e93ccb520101539c2f41312e0635b3)) 66 | 67 | ## [1.5.2](https://github.com/ColinKennedy/nvim-best-practices-plugin-template/compare/v1.5.1...v1.5.2) (2025-01-11) 68 | 69 | 70 | ### Bug Fixes 71 | 72 | * **logging:** fixed fmt_warning calls + others ([0e44922](https://github.com/ColinKennedy/nvim-best-practices-plugin-template/commit/0e449223755ab4ea4fa170753d4b38731e4279b2)) 73 | * **rockspec:** the .rockspec should now publish to LuaRocks correctly ([7f995ef](https://github.com/ColinKennedy/nvim-best-practices-plugin-template/commit/7f995ef9b4459bb47976303825adb7a81737f404)) 74 | 75 | ## [1.5.1](https://github.com/ColinKennedy/nvim-best-practices-plugin-template/compare/v1.5.0...v1.5.1) (2024-12-23) 76 | 77 | 78 | ### Bug Fixes 79 | 80 | * **ci:** urlchecker ignored during release ([f4faf3b](https://github.com/ColinKennedy/nvim-best-practices-plugin-template/commit/f4faf3b557c3fff211195bdc05003b701e895196)) 81 | 82 | ## [1.5.0](https://github.com/ColinKennedy/nvim-best-practices-plugin-template/compare/v1.4.0...v1.5.0) (2024-12-13) 83 | 84 | 85 | ### Features 86 | 87 | * **dependencies:** moved cmdparse + logging out ([83458f2](https://github.com/ColinKennedy/nvim-best-practices-plugin-template/commit/83458f230192cb1a77778de051bcde757e73d756)) 88 | 89 | 90 | ### Bug Fixes 91 | 92 | * **missing:** added missing .gitignore ([6def002](https://github.com/ColinKennedy/nvim-best-practices-plugin-template/commit/6def002a458edbf6dcaf786120b3e3f3fbcd869b)) 93 | 94 | ## [1.4.0](https://github.com/ColinKennedy/nvim-best-practices-plugin-template/compare/v1.3.2...v1.4.0) (2024-12-05) 95 | 96 | 97 | ### Features 98 | 99 | * **dependencies:** removed unneeded git submodules ([58bba30](https://github.com/ColinKennedy/nvim-best-practices-plugin-template/commit/58bba307be8ec2a963b8d0ec256d60d9c14abe16)) 100 | 101 | ## [1.3.2](https://github.com/ColinKennedy/nvim-best-practices-plugin-template/compare/v1.3.1...v1.3.2) (2024-11-12) 102 | 103 | 104 | ### Bug Fixes 105 | 106 | * **luals:** Added missing diagnostics paths ([d5f93ef](https://github.com/ColinKennedy/nvim-best-practices-plugin-template/commit/d5f93ef89c47ae5dd09c684526f7050a0f829e11)) 107 | 108 | ## [1.3.1](https://github.com/ColinKennedy/nvim-best-practices-plugin-template/compare/v1.3.0...v1.3.1) (2024-10-26) 109 | 110 | 111 | ### Bug Fixes 112 | 113 | * **urlchecker:** Added README.md + CHANGELOG.md as a checker ([a4b7d41](https://github.com/ColinKennedy/nvim-best-practices-plugin-template/commit/a4b7d410f4d853d7bf98e4ca6dc198f6ea29bb8d)) 114 | 115 | ## [1.3.0](https://github.com/ColinKennedy/nvim-best-practices-plugin-template/compare/v1.2.0...v1.3.0) (2024-10-26) 116 | 117 | 118 | ### Features 119 | 120 | * **ci:** Added urlchecker.yml ([764f485](https://github.com/ColinKennedy/nvim-best-practices-plugin-template/commit/764f4859522c6c810e75bd82eda6073ef4fc0c0c)) 121 | 122 | ## [1.2.0](https://github.com/ColinKennedy/nvim-best-practices-plugin-template/compare/v1.1.0...v1.2.0) (2024-10-26) 123 | 124 | 125 | ### Features 126 | 127 | * **ci:** Enabled llscheck.yml ([18273bf](https://github.com/ColinKennedy/nvim-best-practices-plugin-template/commit/18273bf3526364ca05d2798318b86f59a3c124e8)) 128 | 129 | ## [1.1.0](https://github.com/ColinKennedy/nvim-best-practices-plugin-template/compare/v1.0.3...v1.1.0) (2024-10-26) 130 | 131 | 132 | ### Features 133 | 134 | * **ci:** Added better GitHub workflows ([da48f5a](https://github.com/ColinKennedy/nvim-best-practices-plugin-template/commit/da48f5a27fb01e9c597d82931e551d10c31b94d0)) 135 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2023 Ellison 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /Makefile: -------------------------------------------------------------------------------- 1 | .PHONY: api-documentation download-dependencies llscheck luacheck stylua test 2 | 3 | # Git will error if the repository already exists. We ignore the error. 4 | # NOTE: We still print out that we did the clone to the user so that they know. 5 | # 6 | ifeq ($(OS),Windows_NT) 7 | IGNORE_EXISTING = 8 | else 9 | IGNORE_EXISTING = 2> /dev/null || true 10 | endif 11 | 12 | CONFIGURATION = .luarc.json 13 | 14 | download-dependencies: 15 | git clone git@github.com:Bilal2453/luvit-meta.git .dependencies/luvit-meta $(IGNORE_EXISTING) 16 | git clone git@github.com:ColinKennedy/mega.cmdparse.git .dependencies/mega.cmdparse $(IGNORE_EXISTING) 17 | git clone git@github.com:ColinKennedy/mega.logging.git .dependencies/mega.logging $(IGNORE_EXISTING) 18 | git clone git@github.com:LuaCATS/busted.git .dependencies/busted $(IGNORE_EXISTING) 19 | git clone git@github.com:LuaCATS/luassert.git .dependencies/luassert $(IGNORE_EXISTING) 20 | 21 | api-documentation: 22 | nvim -u scripts/make_api_documentation/minimal_init.lua -l scripts/make_api_documentation/main.lua 23 | 24 | llscheck: download-dependencies 25 | VIMRUNTIME="`nvim --clean --headless --cmd 'lua io.write(os.getenv("VIMRUNTIME"))' --cmd 'quit'`" llscheck --configpath $(CONFIGURATION) . 26 | 27 | luacheck: 28 | luacheck lua plugin scripts spec 29 | 30 | check-stylua: 31 | stylua lua plugin scripts spec --color always --check 32 | 33 | stylua: 34 | stylua lua plugin scripts spec 35 | 36 | test: download-dependencies 37 | busted . 38 | 39 | # IMPORTANT: Make sure to run this first 40 | # ``` 41 | # luarocks install busted 42 | # luarocks install luacov 43 | # luarocks install luacov-multiple 44 | # ``` 45 | # 46 | coverage-html: download-dependencies 47 | nvim -u NONE -U NONE -N -i NONE --headless -c "luafile scripts/luacov.lua" -c "quit" 48 | luacov --reporter multiple.html 49 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # A Neovim Plugin Template 2 | 3 | A template repository used to create Neovim plugins. 4 | 5 | | | | 6 | |--------------|-------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------| 7 | | Build Status | [![unittests](https://img.shields.io/github/actions/workflow/status/ColinKennedy/nvim-best-practices-plugin-template/test.yml?branch=main&style=for-the-badge&label=Unittests)](https://github.com/ColinKennedy/nvim-best-practices-plugin-template/actions/workflows/test.yml) [![documentation](https://img.shields.io/github/actions/workflow/status/ColinKennedy/nvim-best-practices-plugin-template/documentation.yml?branch=main&style=for-the-badge&label=Documentation)](https://github.com/ColinKennedy/nvim-best-practices-plugin-template/actions/workflows/documentation.yml) [![luacheck](https://img.shields.io/github/actions/workflow/status/ColinKennedy/nvim-best-practices-plugin-template/luacheck.yml?branch=main&style=for-the-badge&label=Luacheck)](https://github.com/ColinKennedy/nvim-best-practices-plugin-template/actions/workflows/luacheck.yml) [![llscheck](https://img.shields.io/github/actions/workflow/status/ColinKennedy/nvim-best-practices-plugin-template/llscheck.yml?branch=main&style=for-the-badge&label=llscheck)](https://github.com/ColinKennedy/nvim-best-practices-plugin-template/actions/workflows/llscheck.yml) [![checkhealth](https://img.shields.io/github/actions/workflow/status/ColinKennedy/nvim-best-practices-plugin-template/checkhealth.yml?branch=main&style=for-the-badge&label=checkhealth)](https://github.com/ColinKennedy/nvim-best-practices-plugin-template/actions/workflows/checkhealth.yml) [![stylua](https://img.shields.io/github/actions/workflow/status/ColinKennedy/nvim-best-practices-plugin-template/stylua.yml?branch=main&style=for-the-badge&label=Stylua)](https://github.com/ColinKennedy/nvim-best-practices-plugin-template/actions/workflows/stylua.yml) [![urlchecker](https://img.shields.io/github/actions/workflow/status/ColinKennedy/nvim-best-practices-plugin-template/urlchecker.yml?branch=main&style=for-the-badge&label=URLChecker)](https://github.com/ColinKennedy/nvim-best-practices-plugin-template/actions/workflows/urlchecker.yml) | 8 | | License | [![License-MIT](https://img.shields.io/badge/License-MIT-blue?style=for-the-badge)](https://github.com/ColinKennedy/nvim-best-practices-plugin-template/blob/main/LICENSE) | 9 | | Social | [![RSS](https://img.shields.io/badge/rss-F88900?style=for-the-badge&logo=rss&logoColor=white)](https://github.com/ColinKennedy/nvim-best-practices-plugin-template/commits/main/doc/news.txt.atom) | 10 | 11 | 12 | # Features 13 | - Follows [nvim-best-practices](https://github.com/nvim-neorocks/nvim-best-practices) 14 | - Fast start-up (~1 ms) 15 | - Auto-release to [luarocks](https://luarocks.org) & [GitHub](https://github.com/ColinKennedy/nvim-best-practices-plugin-template/releases) 16 | - Automated user documentation (using [panvimdoc](https://github.com/kdheepak/panvimdoc)) 17 | - Automated API documentation (using [mini.doc](https://github.com/echasnovski/mini.doc)) 18 | - Automated HTML documentation + self-publishing using [emmylua_doc_cli](https://github.com/CppCXY/emmylua-analyzer-rust/tree/main/crates/emmylua_doc_cli) & [mkdocs-material](https://github.com/squidfunk/mkdocs-material) 19 | - Yes, this repository has a website! Check it out at [nvim-best-practices-plugin-template](https://colinkennedy.github.io/nvim-best-practices-plugin-template)! 20 | - Vimtags generation 21 | - Built-in Vim commands 22 | - A high quality command mode parser 23 | - Auto-completes your commands at any cursor position 24 | - No external dependencies[*](https://github.com/ColinKennedy/nvim-best-practices-plugin-template/wiki/External-Dependencies-Disclaimer) 25 | - [LuaCATS](https://luals.github.io/wiki/annotations/) annotations and type-hints, everywhere 26 | - [RSS feed support](https://github.com/ColinKennedy/nvim-best-practices-plugin-template/commits/main/doc/news.txt.atom) 27 | - Built-in logging to stdout / files 28 | - Unittests use the full power of native [busted](https://github.com/lunarmodules/busted) 29 | - Supports [LuaCov](https://luarocks.org/modules/mpeterv/luacov) for coverage reports! 30 | - Automated testing matrix supports 12 Neovim/OS combinations 31 | - neovim: `[v0.10.0, v0.11.0, stable, nightly]` 32 | - os: `[ubuntu-latest, macos-latest, windows-latest]` 33 | - 100% Lua 34 | - Uses [Semantic Versioning](https://semver.org) 35 | - Integrations 36 | - [lualine.nvim](https://github.com/nvim-lualine/lualine.nvim) 37 | - [telescope.nvim](https://github.com/nvim-telescope/telescope.nvim) 38 | - [`:checkhealth`](https://github.com/ColinKennedy/nvim-best-practices-plugin-template/actions/workflows/checkhealth.yml) 39 | - Github actions for: 40 | - [StyLua](https://github.com/JohnnyMorganz/StyLua) - Auto-formats Lua code 41 | - [llscheck](https://github.com/jeffzi/llscheck) - Checks for Lua type mismatches 42 | - [luacheck](https://github.com/mpeterv/luacheck) - Checks for Lua code issues 43 | - [luarocks](https://luarocks.org) auto-release ([LUAROCKS_API_KEY secret](https://github.com/nvim-neorocks/sample-luarocks-plugin?tab=readme-ov-file#publishing-to-luarocks) configuration required) 44 | - [GitHub](https://github.com/ColinKennedy/nvim-best-practices-plugin-template/releases) auto-release ([PERSONAL_ACCESS_TOKEN secret](https://github.com/nvim-neorocks/sample-luarocks-plugin?tab=readme-ov-file#installing-release-please-recommended) configuration required) 45 | - [mini.doc](https://github.com/echasnovski/mini.doc) - API documentation auto-generator 46 | - [panvimdoc](https://github.com/kdheepak/panvimdoc) - User documentation auto-generator 47 | - [emmylua_doc_cli](https://github.com/CppCXY/emmylua-analyzer-rust/tree/main/crates/emmylua_doc_cli) & [mkdocs-material](https://github.com/squidfunk/mkdocs-material) - Generate HTML from Lua files automatically 48 | - [urlchecker](https://github.com/urlstechie/urlchecker-action) - Checks for broken URL links 49 | - PR reviews - Reminds users to update `doc/news.txt` 50 | 51 | 52 | # Using This Template 53 | 1. Follow the [Wiki instructions](https://github.com/ColinKennedy/nvim-best-practices-plugin-template/wiki/Using-This-Template) 54 | 2. Run `make download-dependencies` so all the Lua LSP features work as expected. 55 | 3. Once you're done, remove this section (the rest of this README.md file should be kept / customized to your needs) 56 | 57 | 58 | # Installation 59 | 60 | - [lazy.nvim](https://github.com/folke/lazy.nvim) 61 | ```lua 62 | { 63 | "ColinKennedy/nvim-best-practices-plugin-template", 64 | dependencies = { "ColinKennedy/mega.cmdparse", "ColinKennedy/mega.logging" }, 65 | -- TODO: (you) - Make sure your first release matches v1.0.0 so it auto-releases! 66 | version = "v1.*", 67 | } 68 | ``` 69 | 70 | 71 | # Configuration 72 | (These are default values) 73 | 74 | 75 | 76 | - [lazy.nvim](https://github.com/folke/lazy.nvim) 77 | ```lua 78 | { 79 | "ColinKennedy/nvim-best-practices-plugin-template", 80 | config = function() 81 | vim.g.plugin_template_configuration = { 82 | commands = { 83 | goodnight_moon = { read = { phrase = "A good book" } }, 84 | hello_world = { 85 | say = { ["repeat"] = 1, style = "lowercase" }, 86 | }, 87 | }, 88 | logging = { 89 | level = "info", 90 | use_console = false, 91 | use_file = false, 92 | }, 93 | tools = { 94 | lualine = { 95 | arbitrary_thing = { 96 | color = "Visual", 97 | text = " Arbitrary Thing", 98 | }, 99 | copy_logs = { 100 | color = "Comment", 101 | text = "󰈔 Copy Logs", 102 | }, 103 | goodnight_moon = { 104 | color = "Question", 105 | text = " Goodnight moon", 106 | }, 107 | hello_world = { 108 | color = "Title", 109 | text = " Hello, World!", 110 | }, 111 | }, 112 | telescope = { 113 | goodnight_moon = { 114 | { "Foo Book", "Author A" }, 115 | { "Bar Book Title", "John Doe" }, 116 | { "Fizz Drink", "Some Name" }, 117 | { "Buzz Bee", "Cool Person" }, 118 | }, 119 | hello_world = { "Hi there!", "Hello, Sailor!", "What's up, doc?" }, 120 | }, 121 | }, 122 | } 123 | end 124 | } 125 | ``` 126 | 127 | 128 | ## Lualine 129 | 130 | 131 | 132 | > Note: You can customize lualine colors here or using 133 | > `vim.g.plugin_template_configuration`. 134 | 135 | [lualine.nvim](https://github.com/nvim-lualine/lualine.nvim) 136 | ```lua 137 | require("lualine").setup { 138 | sections = { 139 | lualine_y = { 140 | -- ... Your other configuration ... 141 | { 142 | "plugin_template", 143 | -- NOTE: These will override default values 144 | -- display = { 145 | -- goodnight_moon = {color={fg="#FFFFFF"}, text="Custom message 1"}}, 146 | -- hello_world = {color={fg="#333333"}, text="Custom message 2"}, 147 | -- }, 148 | }, 149 | } 150 | } 151 | } 152 | ``` 153 | 154 | 155 | ## Telescope 156 | 157 | 158 | 159 | > Note: You can customize telescope colors here or using 160 | > `vim.g.plugin_template_configuration`. 161 | 162 | [telescope.nvim](https://github.com/nvim-telescope/telescope.nvim) 163 | ```lua 164 | { 165 | "nvim-telescope/telescope.nvim", 166 | cmd = "Telescope", 167 | config = function() 168 | -- ... Your other configuration ... 169 | require("telescope").load_extension("plugin_template") 170 | end, 171 | dependencies = { 172 | "ColinKennedy/nvim-best-practices-plugin-template", 173 | "nvim-lua/plenary.nvim", 174 | }, 175 | version = "0.1.*", 176 | }, 177 | ``` 178 | 179 | 180 | ### Colors 181 | This plugin provides two default highlights 182 | 183 | - `PluginTemplateTelescopeEntry` 184 | - `PluginTemplateTelescopeSecondary` 185 | 186 | Both come with default colors that should look nice. If you want to change them, here's how: 187 | ```lua 188 | vim.api.nvim_set_hl(0, "PluginTemplateTelescopeEntry", {link="Statement"}) 189 | vim.api.nvim_set_hl(0, "PluginTemplateTelescopeSecondary", {link="Question"}) 190 | ``` 191 | 192 | 193 | # Commands 194 | Here are some example commands: 195 | 196 | 197 | 198 | 199 | ```vim 200 | " A typical subcommand 201 | :PluginTemplate hello-world say phrase "Hello, World!" " How are you?" 202 | :PluginTemplate hello-world say phrase "Hello, World!" --repeat=2 --style=lowercase 203 | 204 | " An example of a flag this repeatable and 3 flags, -a, -b, -c, as one dash 205 | :PluginTemplate arbitrary-thing -vvv -abc -f 206 | 207 | " Separate commands with completely separate, flexible APIs 208 | :PluginTemplate goodnight-moon count-sheep 42 209 | :PluginTemplate goodnight-moon read "a book" 210 | :PluginTemplate goodnight-moon sleep -z -z -z 211 | ``` 212 | 213 | 214 | # Tests 215 | ## Initialization 216 | Run this line once before calling any `busted` command 217 | 218 | ```sh 219 | eval $(luarocks path --lua-version 5.1 --bin) 220 | ``` 221 | 222 | 223 | ## Running 224 | Run all tests 225 | ```sh 226 | # Using the package manager 227 | luarocks test --test-type busted 228 | # Or manually 229 | busted . 230 | # Or with Make 231 | make test 232 | ``` 233 | 234 | Run test based on tags 235 | ```sh 236 | busted . --tags=simple 237 | ``` 238 | 239 | 240 | # Coverage 241 | Making sure that your plugin is well tested is important. 242 | `nvim-best-practices-plugin-template` can generate a per-line breakdown of exactly where 243 | your code is lacking tests using [LuaCov](https://luarocks.org/modules/mpeterv/luacov). 244 | 245 | 246 | ## Setup 247 | Make sure to install all dependencies for the unittests + coverage reporter if 248 | you have not installed them already. 249 | 250 | ```sh 251 | luarocks install busted --local 252 | luarocks install luacov --local 253 | luarocks install luacov-multiple --local 254 | ``` 255 | 256 | 257 | ## Running 258 | ```sh 259 | make coverage-html 260 | ``` 261 | 262 | This will generate a `luacov.stats.out` & `luacov_html/` directory. 263 | 264 | 265 | ## Viewing 266 | ```sh 267 | (cd luacov_html && python -m http.server) 268 | ``` 269 | 270 | If it worked, you should see a message like 271 | "Serving HTTP on 0.0.0.0 port 8000 (http://0.0.0.0:8000) ..." 272 | Open `http://0.0.0.0:8000` in a browser like 273 | [Firefox](https://www.mozilla.org/en-US/firefox) and you should see a view like this: 274 | 275 | ![Image](https://github.com/user-attachments/assets/e5b30df8-036a-4886-81b9-affbf5c9e32a) 276 | 277 | Just navigate down a few folders until you get to a .lua file and you'll see a breakdown 278 | of your line coverage like this: 279 | 280 | ![Image](https://github.com/user-attachments/assets/c5420b16-4be7-4177-92c7-01af0b418816) 281 | 282 | 283 | 284 | # Tracking Updates 285 | See [doc/news.txt](doc/news.txt) for updates. 286 | 287 | You can watch this plugin for changes by adding this URL to your RSS feed: 288 | ``` 289 | https://github.com/ColinKennedy/nvim-best-practices-plugin-template/commits/main/doc/news.txt.atom 290 | ``` 291 | 292 | 293 | # Other Plugins 294 | This template is full of various features. But if your plugin is only meant to 295 | be a simple plugin and you don't want the bells and whistles that this template 296 | provides, consider instead using 297 | [nvim-plugin-template](https://github.com/ellisonleao/nvim-plugin-template) 298 | -------------------------------------------------------------------------------- /doc/news.txt: -------------------------------------------------------------------------------- 1 | *plugin-template-news.txt* 2 | 3 | Notable changes since PluginTemplate 1.0 4 | 5 | 6 | =============================================================================== 7 | NEW FEATURES *plugin-template-new-features* 8 | 9 | - Added llscheck.yml - A GitHub workflow that detects type annotation issues! 10 | - Added urlchecker.yml - A GitHub workflow that finds broken URLs! 11 | 12 | 13 | =============================================================================== 14 | BREAKING CHANGES *plugin-template-new-breaking* 15 | 16 | n/a 17 | 18 | 19 | vim:tw=78:ts=8:noet:ft=help:norl: 20 | -------------------------------------------------------------------------------- /doc/plugin-template.txt: -------------------------------------------------------------------------------- 1 | *plugin-template.txt* For Neovim >= 0.8.0 Last change: 2025 May 05 2 | 3 | ============================================================================== 4 | Table of Contents *plugin-template-table-of-contents* 5 | 6 | 1. A Neovim Plugin Template |plugin-template-a-neovim-plugin-template| 7 | 2. Features |plugin-template-features| 8 | 3. Using This Template |plugin-template-using-this-template| 9 | 4. Installation |plugin-template-installation| 10 | 5. Configuration |plugin-template-configuration| 11 | - Lualine |plugin-template-configuration-lualine| 12 | - Telescope |plugin-template-configuration-telescope| 13 | 6. Commands |plugin-template-commands| 14 | 7. Tests |plugin-template-tests| 15 | - Initialization |plugin-template-tests-initialization| 16 | - Running |plugin-template-tests-running| 17 | 8. Coverage |plugin-template-coverage| 18 | - Setup |plugin-template-coverage-setup| 19 | - Running |plugin-template-coverage-running| 20 | - Viewing |plugin-template-coverage-viewing| 21 | 9. Tracking Updates |plugin-template-tracking-updates| 22 | 10. Other Plugins |plugin-template-other-plugins| 23 | 11. Links |plugin-template-links| 24 | 25 | ============================================================================== 26 | 1. A Neovim Plugin Template *plugin-template-a-neovim-plugin-template* 27 | 28 | A template repository used to create Neovim plugins. 29 | 30 | -------------------------------------------------------------------------------- 31 | 32 | --------- ---------------------------------------------------------------------- 33 | Build [unittests] [documentation] [luacheck] [llscheck] [checkhealth] 34 | Status [stylua] [urlchecker] 35 | 36 | License [License-MIT] 37 | 38 | Social [RSS] 39 | -------------------------------------------------------------------------------- 40 | 41 | ============================================================================== 42 | 2. Features *plugin-template-features* 43 | 44 | - Follows nvim-best-practices 45 | - Fast start-up (~1 ms) 46 | - Auto-release to luarocks & GitHub 47 | - Automated user documentation (using panvimdoc ) 48 | - Automated API documentation (using mini.doc ) 49 | - Automated HTML documentation + self-publishing using emmylua_doc_cli & mkdocs-material 50 | - Yes, this repository has a website! Check it out at nvim-best-practices-plugin-template ! 51 | - Vimtags generation 52 | - Built-in Vim commands 53 | - A high quality command mode parser 54 | - Auto-completes your commands at any cursor position 55 | - No external dependencies 56 | - LuaCATS annotations and type-hints, everywhere 57 | - RSS feed support 58 | - Built-in logging to stdout / files 59 | - Unittests use the full power of native busted 60 | - Supports LuaCov for coverage reports! 61 | - Automated testing matrix supports 12 Neovim/OS combinations 62 | - neovim: `[v0.10.0, v0.11.0, stable, nightly]` 63 | - os: `[ubuntu-latest, macos-latest, windows-latest]` 64 | - 100% Lua 65 | - Uses Semantic Versioning 66 | - Integrations 67 | - lualine.nvim 68 | - telescope.nvim 69 | - `:checkhealth` 70 | - Github actions for: 71 | - StyLua - Auto-formats Lua code 72 | - llscheck - Checks for Lua type mismatches 73 | - luacheck - Checks for Lua code issues 74 | - luarocks auto-release (LUAROCKS_API_KEY secret configuration required) 75 | - GitHub auto-release (PERSONAL_ACCESS_TOKEN secret configuration required) 76 | - mini.doc - API documentation auto-generator 77 | - panvimdoc - User documentation auto-generator 78 | - emmylua_doc_cli & mkdocs-material - Generate HTML from Lua files automatically 79 | - urlchecker - Checks for broken URL links 80 | - PR reviews - Reminds users to update `doc/news.txt` 81 | 82 | 83 | ============================================================================== 84 | 3. Using This Template *plugin-template-using-this-template* 85 | 86 | 1. Follow the Wiki instructions 87 | 2. Run `make download-dependencies` so all the Lua LSP features work as expected. 88 | 3. Once you’re done, remove this section (the rest of this README.md file should be kept / customized to your needs) 89 | 90 | 91 | ============================================================================== 92 | 4. Installation *plugin-template-installation* 93 | 94 | - lazy.nvim 95 | 96 | >lua 97 | { 98 | "ColinKennedy/nvim-best-practices-plugin-template", 99 | dependencies = { "ColinKennedy/mega.cmdparse", "ColinKennedy/mega.logging" }, 100 | -- TODO: (you) - Make sure your first release matches v1.0.0 so it auto-releases! 101 | version = "v1.*", 102 | } 103 | < 104 | 105 | 106 | ============================================================================== 107 | 5. Configuration *plugin-template-configuration* 108 | 109 | (These are default values) 110 | 111 | - lazy.nvim 112 | 113 | >lua 114 | { 115 | "ColinKennedy/nvim-best-practices-plugin-template", 116 | config = function() 117 | vim.g.plugin_template_configuration = { 118 | commands = { 119 | goodnight_moon = { read = { phrase = "A good book" } }, 120 | hello_world = { 121 | say = { ["repeat"] = 1, style = "lowercase" }, 122 | }, 123 | }, 124 | logging = { 125 | level = "info", 126 | use_console = false, 127 | use_file = false, 128 | }, 129 | tools = { 130 | lualine = { 131 | arbitrary_thing = { 132 | color = "Visual", 133 | text = " Arbitrary Thing", 134 | }, 135 | copy_logs = { 136 | color = "Comment", 137 | text = "󰈔 Copy Logs", 138 | }, 139 | goodnight_moon = { 140 | color = "Question", 141 | text = " Goodnight moon", 142 | }, 143 | hello_world = { 144 | color = "Title", 145 | text = " Hello, World!", 146 | }, 147 | }, 148 | telescope = { 149 | goodnight_moon = { 150 | { "Foo Book", "Author A" }, 151 | { "Bar Book Title", "John Doe" }, 152 | { "Fizz Drink", "Some Name" }, 153 | { "Buzz Bee", "Cool Person" }, 154 | }, 155 | hello_world = { "Hi there!", "Hello, Sailor!", "What's up, doc?" }, 156 | }, 157 | }, 158 | } 159 | end 160 | } 161 | < 162 | 163 | 164 | LUALINE *plugin-template-configuration-lualine* 165 | 166 | 167 | Note: You can customize lualine colors here or using 168 | `vim.g.plugin_template_configuration`. 169 | lualine.nvim 170 | 171 | >lua 172 | require("lualine").setup { 173 | sections = { 174 | lualine_y = { 175 | -- ... Your other configuration ... 176 | { 177 | "plugin_template", 178 | -- NOTE: These will override default values 179 | -- display = { 180 | -- goodnight_moon = {color={fg="#FFFFFF"}, text="Custom message 1"}}, 181 | -- hello_world = {color={fg="#333333"}, text="Custom message 2"}, 182 | -- }, 183 | }, 184 | } 185 | } 186 | } 187 | < 188 | 189 | 190 | TELESCOPE *plugin-template-configuration-telescope* 191 | 192 | 193 | Note: You can customize telescope colors here or using 194 | `vim.g.plugin_template_configuration`. 195 | telescope.nvim 196 | 197 | >lua 198 | { 199 | "nvim-telescope/telescope.nvim", 200 | cmd = "Telescope", 201 | config = function() 202 | -- ... Your other configuration ... 203 | require("telescope").load_extension("plugin_template") 204 | end, 205 | dependencies = { 206 | "ColinKennedy/nvim-best-practices-plugin-template", 207 | "nvim-lua/plenary.nvim", 208 | }, 209 | version = "0.1.*", 210 | }, 211 | < 212 | 213 | 214 | COLORS ~ 215 | 216 | This plugin provides two default highlights 217 | 218 | - `PluginTemplateTelescopeEntry` 219 | - `PluginTemplateTelescopeSecondary` 220 | 221 | Both come with default colors that should look nice. If you want to change 222 | them, here’s how: 223 | 224 | >lua 225 | vim.api.nvim_set_hl(0, "PluginTemplateTelescopeEntry", {link="Statement"}) 226 | vim.api.nvim_set_hl(0, "PluginTemplateTelescopeSecondary", {link="Question"}) 227 | < 228 | 229 | 230 | ============================================================================== 231 | 6. Commands *plugin-template-commands* 232 | 233 | Here are some example commands: 234 | 235 | >vim 236 | " A typical subcommand 237 | :PluginTemplate hello-world say phrase "Hello, World!" " How are you?" 238 | :PluginTemplate hello-world say phrase "Hello, World!" --repeat=2 --style=lowercase 239 | 240 | " An example of a flag this repeatable and 3 flags, -a, -b, -c, as one dash 241 | :PluginTemplate arbitrary-thing -vvv -abc -f 242 | 243 | " Separate commands with completely separate, flexible APIs 244 | :PluginTemplate goodnight-moon count-sheep 42 245 | :PluginTemplate goodnight-moon read "a book" 246 | :PluginTemplate goodnight-moon sleep -z -z -z 247 | < 248 | 249 | 250 | ============================================================================== 251 | 7. Tests *plugin-template-tests* 252 | 253 | 254 | INITIALIZATION *plugin-template-tests-initialization* 255 | 256 | Run this line once before calling any `busted` command 257 | 258 | >sh 259 | eval $(luarocks path --lua-version 5.1 --bin) 260 | < 261 | 262 | 263 | RUNNING *plugin-template-tests-running* 264 | 265 | Run all tests 266 | 267 | >sh 268 | # Using the package manager 269 | luarocks test --test-type busted 270 | # Or manually 271 | busted . 272 | # Or with Make 273 | make test 274 | < 275 | 276 | Run test based on tags 277 | 278 | >sh 279 | busted . --tags=simple 280 | < 281 | 282 | 283 | ============================================================================== 284 | 8. Coverage *plugin-template-coverage* 285 | 286 | Making sure that your plugin is well tested is important. 287 | `nvim-best-practices-plugin-template` can generate a per-line breakdown of 288 | exactly where your code is lacking tests using LuaCov 289 | . 290 | 291 | 292 | SETUP *plugin-template-coverage-setup* 293 | 294 | Make sure to install all dependencies for the unittests + coverage reporter if 295 | you have not installed them already. 296 | 297 | >sh 298 | luarocks install busted --local 299 | luarocks install luacov --local 300 | luarocks install luacov-multiple --local 301 | < 302 | 303 | 304 | RUNNING *plugin-template-coverage-running* 305 | 306 | >sh 307 | make coverage-html 308 | < 309 | 310 | This will generate a `luacov.stats.out` & `luacov_html/` directory. 311 | 312 | 313 | VIEWING *plugin-template-coverage-viewing* 314 | 315 | >sh 316 | (cd luacov_html && python -m http.server) 317 | < 318 | 319 | If it worked, you should see a message like "Serving HTTP on 0.0.0.0 port 8000 320 | …" Open `http://0.0.0.0:8000` in a browser like Firefox 321 | and you should see a view like this: 322 | 323 | Just navigate down a few folders until you get to a .lua file and you’ll see 324 | a breakdown of your line coverage like this: 325 | 326 | 327 | ============================================================================== 328 | 9. Tracking Updates *plugin-template-tracking-updates* 329 | 330 | See doc/news.txt for updates. 331 | 332 | You can watch this plugin for changes by adding this URL to your RSS feed: 333 | 334 | > 335 | https://github.com/ColinKennedy/nvim-best-practices-plugin-template/commits/main/doc/news.txt.atom 336 | < 337 | 338 | 339 | ============================================================================== 340 | 10. Other Plugins *plugin-template-other-plugins* 341 | 342 | This template is full of various features. But if your plugin is only meant to 343 | be a simple plugin and you don’t want the bells and whistles that this 344 | template provides, consider instead using nvim-plugin-template 345 | 346 | 347 | ============================================================================== 348 | 11. Links *plugin-template-links* 349 | 350 | 1. *Image*: https://github.com/user-attachments/assets/e5b30df8-036a-4886-81b9-affbf5c9e32a 351 | 2. *Image*: https://github.com/user-attachments/assets/c5420b16-4be7-4177-92c7-01af0b418816 352 | 353 | Generated by panvimdoc 354 | 355 | vim:tw=78:ts=8:noet:ft=help:norl: 356 | -------------------------------------------------------------------------------- /doc/plugin_template_api.txt: -------------------------------------------------------------------------------- 1 | ============================================================================== 2 | ------------------------------------------------------------------------------ 3 | All function(s) that can be called externally by other Lua modules. 4 | 5 | If a function's signature here changes in some incompatible way, this 6 | package must get a new **major** version. 7 | 8 | ------------------------------------------------------------------------------ 9 | *plugin_template.run_arbitrary_thing()* 10 | 11 | `run_arbitrary_thing`({names}) 12 | 13 | Print the `names`. 14 | 15 | Parameters ~ 16 | {names} `(string)`[]? Some text to print out. e.g. `{"a", "b", "c"}`. 17 | 18 | ------------------------------------------------------------------------------ 19 | *plugin_template.run_copy_logs()* 20 | 21 | `run_copy_logs`({path}) 22 | 23 | Copy the log data from the given `path` to the user's clipboard. 24 | 25 | Parameters ~ 26 | {path} `(string)`? 27 | A path on-disk to look for logs. If none is given, the default fallback 28 | location is used instead. 29 | 30 | ------------------------------------------------------------------------------ 31 | *plugin_template.run_hello_world_say_phrase()* 32 | 33 | `run_hello_world_say_phrase`({phrase}, {repeat_}, {style}) 34 | 35 | Print `phrase` according to the other options. 36 | 37 | Parameters ~ 38 | {phrase} `(string)`[] 39 | The text to say. 40 | {repeat_} `(number)`? 41 | A 1-or-more value. The number of times to print `word`. 42 | {style} `(string)`? 43 | Control how the text should be shown. 44 | 45 | ------------------------------------------------------------------------------ 46 | *plugin_template.run_hello_world_say_word()* 47 | 48 | `run_hello_world_say_word`({word}, {repeat_}, {style}) 49 | 50 | Print `phrase` according to the other options. 51 | 52 | Parameters ~ 53 | {word} `(string)` 54 | The text to say. 55 | {repeat_} `(number)`? 56 | A 1-or-more value. The number of times to print `word`. 57 | {style} `(string)`? 58 | Control how the text should be shown. 59 | 60 | ------------------------------------------------------------------------------ 61 | *plugin_template.run_goodnight_moon_count_sheep()* 62 | 63 | `run_goodnight_moon_count_sheep`({count}) 64 | 65 | Count a sheep for each `count`. 66 | 67 | Parameters ~ 68 | {count} `(number)` Prints 1 sheep per `count`. A value that is 1-or-greater. 69 | 70 | ------------------------------------------------------------------------------ 71 | *plugin_template.run_goodnight_moon_read()* 72 | 73 | `run_goodnight_moon_read`({book}) 74 | 75 | Print the name of the book. 76 | 77 | Parameters ~ 78 | {book} `(string)` The name of the book. 79 | 80 | ------------------------------------------------------------------------------ 81 | *plugin_template.run_goodnight_moon_sleep()* 82 | 83 | `run_goodnight_moon_sleep`({count}) 84 | 85 | Print Zzz each `count`. 86 | 87 | Parameters ~ 88 | {count} `(number)`? Prints 1 Zzz per `count`. A value that is 1-or-greater. 89 | 90 | 91 | WARNING: This file is auto-generated. Do not edit it! 92 | 93 | vim:tw=78:ts=8:noet:ft=help:norl: -------------------------------------------------------------------------------- /doc/plugin_template_types.txt: -------------------------------------------------------------------------------- 1 | ============================================================================== 2 | ------------------------------------------------------------------------------ 3 | A collection of types to be included / used in other Lua files. 4 | 5 | These types are either required by the Lua API or required for the normal 6 | operation of this Lua plugin. 7 | 8 | ------------------------------------------------------------------------------ 9 | *plugin_template.Configuration* 10 | The user's customizations for this plugin. 11 | 12 | Fields ~ 13 | {commands} |plugin_template.ConfigurationCommands|? 14 | Customize the fallback behavior of all `:PluginTemplate` commands. 15 | {logging} |plugin_template.LoggingConfiguration|? 16 | Control how and which logs print to file / Neovim. 17 | {tools} |plugin_template.ConfigurationTools|? 18 | Optional third-party tool integrations. 19 | 20 | ------------------------------------------------------------------------------ 21 | *plugin_template.ConfigurationCommands* 22 | Customize the fallback behavior of all `:PluginTemplate` commands. 23 | 24 | Fields ~ 25 | {goodnight_moon} |plugin_template.ConfigurationGoodnightMoon|? 26 | The default values when a user calls `:PluginTemplate goodnight-moon`. 27 | {hello_world} |plugin_template.ConfigurationHelloWorld|? 28 | The default values when a user calls `:PluginTemplate hello-world`. 29 | 30 | ------------------------------------------------------------------------------ 31 | *plugin_template.ConfigurationGoodnightMoon* 32 | The default values when a user calls `:PluginTemplate goodnight-moon`. 33 | 34 | Fields ~ 35 | {read} |plugin_template.ConfigurationGoodnightMoonRead|? 36 | The default values when a user calls `:PluginTemplate goodnight-moon read`. 37 | 38 | ------------------------------------------------------------------------------ 39 | *plugin_template.LoggingConfiguration* 40 | Control whether or not logging is printed to the console or to disk. 41 | 42 | Fields ~ 43 | {level} ( 44 | | "trace" 45 | | "debug" 46 | | "info" 47 | | "warn" | "error" 48 | | "fatal" 49 | | vim.log.levels.DEBUG 50 | | vim.log.levels.ERROR 51 | | vim.log.levels.INFO 52 | | vim.log.levels.TRACE 53 | | vim.log.levels.WARN)? 54 | Any messages above this level will be logged. 55 | {use_console} `(boolean)`? 56 | Should print the output to neovim while running. Warning: This is very 57 | spammy. You probably don't want to enable this unless you have to. 58 | {use_file} `(boolean)`? 59 | Should write to a file. 60 | {output_path} `(string)`? 61 | The default path on-disk where log files will be written to. 62 | Defaults to "/home/selecaoone/.local/share/nvim/plugin_name.log". 63 | 64 | ------------------------------------------------------------------------------ 65 | *plugin_template.ConfigurationGoodnightMoonRead* 66 | The default values when a user calls `:PluginTemplate goodnight-moon read`. 67 | 68 | Fields ~ 69 | {phrase} `(string)` 70 | The book to read if no book is given by the user. 71 | 72 | ------------------------------------------------------------------------------ 73 | *plugin_template.ConfigurationHelloWorld* 74 | The default values when a user calls `:PluginTemplate hello-world`. 75 | 76 | Fields ~ 77 | {say} |plugin_template.ConfigurationHelloWorldSay|? 78 | The default values when a user calls `:PluginTemplate hello-world say`. 79 | 80 | ------------------------------------------------------------------------------ 81 | *plugin_template.ConfigurationHelloWorldSay* 82 | The default values when a user calls `:PluginTemplate hello-world say`. 83 | 84 | Fields ~ 85 | {repeat} `(number)` 86 | A 1-or-more value. When 1, the phrase is said once. When 2+, the phrase 87 | is repeated that many times. 88 | {style} "lowercase" | "uppercase" 89 | Control how the text is displayed. e.g. "uppercase" changes "hello" to "HELLO". 90 | 91 | ------------------------------------------------------------------------------ 92 | *plugin_template.ConfigurationTools* 93 | Optional third-party tool integrations. 94 | 95 | Fields ~ 96 | {lualine} |plugin_template.ConfigurationToolsLualine|? 97 | A Vim statusline replacement that will show the command that the user just ran. 98 | 99 | ------------------------------------------------------------------------------ 100 | *plugin_template.ConfigurationToolsLualineData* 101 | The display values that will be used when a specific `plugin_template` 102 | command runs. 103 | 104 | Fields ~ 105 | {color} |vim.api.keyset.highlight|? 106 | The foreground/background color to use for the Lualine status. 107 | {prefix} `(string)`? 108 | The text to display in lualine. 109 | 110 | 111 | WARNING: This file is auto-generated. Do not edit it! 112 | 113 | vim:tw=78:ts=8:noet:ft=help:norl: -------------------------------------------------------------------------------- /doc/tags: -------------------------------------------------------------------------------- 1 | plugin-template-a-neovim-plugin-template plugin-template.txt /*plugin-template-a-neovim-plugin-template* 2 | plugin-template-commands plugin-template.txt /*plugin-template-commands* 3 | plugin-template-configuration plugin-template.txt /*plugin-template-configuration* 4 | plugin-template-configuration-lualine plugin-template.txt /*plugin-template-configuration-lualine* 5 | plugin-template-configuration-telescope plugin-template.txt /*plugin-template-configuration-telescope* 6 | plugin-template-coverage plugin-template.txt /*plugin-template-coverage* 7 | plugin-template-coverage-running plugin-template.txt /*plugin-template-coverage-running* 8 | plugin-template-coverage-setup plugin-template.txt /*plugin-template-coverage-setup* 9 | plugin-template-coverage-viewing plugin-template.txt /*plugin-template-coverage-viewing* 10 | plugin-template-features plugin-template.txt /*plugin-template-features* 11 | plugin-template-installation plugin-template.txt /*plugin-template-installation* 12 | plugin-template-links plugin-template.txt /*plugin-template-links* 13 | plugin-template-new-breaking news.txt /*plugin-template-new-breaking* 14 | plugin-template-new-features news.txt /*plugin-template-new-features* 15 | plugin-template-news.txt news.txt /*plugin-template-news.txt* 16 | plugin-template-other-plugins plugin-template.txt /*plugin-template-other-plugins* 17 | plugin-template-table-of-contents plugin-template.txt /*plugin-template-table-of-contents* 18 | plugin-template-tests plugin-template.txt /*plugin-template-tests* 19 | plugin-template-tests-initialization plugin-template.txt /*plugin-template-tests-initialization* 20 | plugin-template-tests-running plugin-template.txt /*plugin-template-tests-running* 21 | plugin-template-tracking-updates plugin-template.txt /*plugin-template-tracking-updates* 22 | plugin-template-using-this-template plugin-template.txt /*plugin-template-using-this-template* 23 | plugin-template.txt plugin-template.txt /*plugin-template.txt* 24 | plugin_template.Configuration plugin_template_types.txt /*plugin_template.Configuration* 25 | plugin_template.ConfigurationCommands plugin_template_types.txt /*plugin_template.ConfigurationCommands* 26 | plugin_template.ConfigurationGoodnightMoon plugin_template_types.txt /*plugin_template.ConfigurationGoodnightMoon* 27 | plugin_template.ConfigurationGoodnightMoonRead plugin_template_types.txt /*plugin_template.ConfigurationGoodnightMoonRead* 28 | plugin_template.ConfigurationHelloWorld plugin_template_types.txt /*plugin_template.ConfigurationHelloWorld* 29 | plugin_template.ConfigurationHelloWorldSay plugin_template_types.txt /*plugin_template.ConfigurationHelloWorldSay* 30 | plugin_template.ConfigurationTools plugin_template_types.txt /*plugin_template.ConfigurationTools* 31 | plugin_template.ConfigurationToolsLualineData plugin_template_types.txt /*plugin_template.ConfigurationToolsLualineData* 32 | plugin_template.LoggingConfiguration plugin_template_types.txt /*plugin_template.LoggingConfiguration* 33 | plugin_template.run_arbitrary_thing() plugin_template_api.txt /*plugin_template.run_arbitrary_thing()* 34 | plugin_template.run_copy_logs() plugin_template_api.txt /*plugin_template.run_copy_logs()* 35 | plugin_template.run_goodnight_moon_count_sheep() plugin_template_api.txt /*plugin_template.run_goodnight_moon_count_sheep()* 36 | plugin_template.run_goodnight_moon_read() plugin_template_api.txt /*plugin_template.run_goodnight_moon_read()* 37 | plugin_template.run_goodnight_moon_sleep() plugin_template_api.txt /*plugin_template.run_goodnight_moon_sleep()* 38 | plugin_template.run_hello_world_say_phrase() plugin_template_api.txt /*plugin_template.run_hello_world_say_phrase()* 39 | plugin_template.run_hello_world_say_word() plugin_template_api.txt /*plugin_template.run_hello_world_say_word()* 40 | -------------------------------------------------------------------------------- /lua/lualine/components/plugin_template.lua: -------------------------------------------------------------------------------- 1 | --- Tell the user which command they just ran, using lualine.nvim 2 | --- 3 | ---@source https://github.com/nvim-lualine/lualine.nvim 4 | --- 5 | 6 | local arbitrary_thing_runner = require("plugin_template._commands.arbitrary_thing.runner") 7 | local configuration = require("plugin_template._core.configuration") 8 | local copy_log_runner = require("plugin_template._commands.copy_logs.runner") 9 | local count_sheep = require("plugin_template._commands.goodnight_moon.count_sheep") 10 | local lualine_require = require("lualine_require") 11 | local modules = lualine_require.lazy_require({ highlight = "lualine.highlight" }) 12 | local read = require("plugin_template._commands.goodnight_moon.read") 13 | local say_runner = require("plugin_template._commands.hello_world.say.runner") 14 | local sleep = require("plugin_template._commands.goodnight_moon.sleep") 15 | local tabler = require("plugin_template._core.tabler") 16 | 17 | local M = require("lualine.component"):extend() 18 | 19 | ---@type string? 20 | M.PREVIOUS_COMMAND = nil 21 | 22 | ---@class plugin_template.LualineConfiguration 23 | --- The Raw user settings from lualine's configuration. 24 | --- e.g. `require("lualine").setup { sections = { { "plugin_template", ... }}}` 25 | --- where "..." is the user's settings. 26 | ---@field display table? 27 | 28 | ---@class plugin_template.LualineDisplayData 29 | --- Any text, icons, etc that will be displayed for `plugin_template` commands. 30 | ---@field prefix string 31 | --- The text to display when a command was called. e.g. " Goodnight moon". 32 | 33 | --- Track the given `command` any time a function (`callers`) in `module` runs. 34 | --- 35 | --- Warning: 36 | --- To prevent unwanted behavior, only call this function one for every 37 | --- unique Lua `module` + caller. 38 | --- 39 | ---@param module table A Lua file to directly edit. 40 | ---@param callers string[] The names of each function(s) to modify. 41 | ---@param command string The command name to track when a function `callers` runs. 42 | --- 43 | local function _patch_runner_commands(module, callers, command) 44 | for _, name in ipairs(callers) do 45 | local original_caller = module[name] 46 | 47 | module[name] = function(...) 48 | M.PREVIOUS_COMMAND = command 49 | 50 | return original_caller(...) 51 | end 52 | end 53 | end 54 | 55 | _patch_runner_commands(arbitrary_thing_runner, { "run" }, "arbitrary_thing") 56 | _patch_runner_commands(copy_log_runner, { "run" }, "copy_logs") 57 | _patch_runner_commands(count_sheep, { "run" }, "goodnight_moon") 58 | _patch_runner_commands(read, { "run" }, "goodnight_moon") 59 | _patch_runner_commands(say_runner, { "run_say_phrase", "run_say_word" }, "hello_world") 60 | _patch_runner_commands(sleep, { "run" }, "goodnight_moon") 61 | 62 | --- Setup all colors / text for lualine to display later. 63 | --- 64 | ---@param options plugin_template.LualineConfiguration? 65 | --- The options to pass from lualine to `plugin_templaet`. 66 | --- 67 | function M:init(options) 68 | configuration.initialize_data_if_needed() 69 | 70 | --- @type table 71 | local data 72 | 73 | if options then 74 | data = options.display or {} 75 | end 76 | 77 | local defaults = tabler.get_value(configuration.DATA, { "tools", "lualine" }) or {} 78 | defaults = vim.tbl_deep_extend("force", defaults, data) 79 | 80 | M.super.init(self, options) 81 | 82 | self._command_text = { 83 | arbitrary_thing = tabler.get_value(defaults, { "arbitrary_thing", "text" }) 84 | or "", 85 | copy_logs = tabler.get_value(defaults, { "copy_logs", "text" }) or "", 86 | hello_world = tabler.get_value(defaults, { "hello_world", "text" }) or "", 87 | goodnight_moon = tabler.get_value(defaults, { "goodnight_moon", "text" }) 88 | or "", 89 | } 90 | 91 | self._highlight_groups = { 92 | arbitrary_thing = modules.highlight.create_component_highlight_group( 93 | defaults.arbitrary_thing.color or "Visual", 94 | "plugin_template_arbitrary_thing", 95 | self.options 96 | ), 97 | copy_logs = modules.highlight.create_component_highlight_group( 98 | defaults.copy_logs.color or "Comment", 99 | "plugin_template_copy_logs", 100 | self.options 101 | ), 102 | goodnight_moon = modules.highlight.create_component_highlight_group( 103 | defaults.goodnight_moon.color or "Question", 104 | "plugin_template_goodnight_moon", 105 | self.options 106 | ), 107 | hello_world = modules.highlight.create_component_highlight_group( 108 | defaults.hello_world.color or "Title", 109 | "plugin_template_hello_world", 110 | self.options 111 | ), 112 | } 113 | end 114 | 115 | ---@return string? # Get the text for the Lualine component. 116 | function M:update_status() 117 | local command = M.PREVIOUS_COMMAND 118 | 119 | if not command then 120 | return nil 121 | end 122 | 123 | local text = self._command_text[command] 124 | local color = self._highlight_groups[M.PREVIOUS_COMMAND] 125 | 126 | if not color then 127 | return text 128 | end 129 | 130 | local prefix = modules.highlight.component_format_highlight(color) 131 | 132 | if not prefix then 133 | return text 134 | end 135 | 136 | return prefix .. text 137 | end 138 | 139 | return M 140 | -------------------------------------------------------------------------------- /lua/plugin_template/_commands/arbitrary_thing/parser.lua: -------------------------------------------------------------------------------- 1 | --- The main parser for the `:PluginTemplate arbitrary-thing` command. 2 | 3 | local cmdparse = require("mega.cmdparse") 4 | 5 | local M = {} 6 | 7 | ---@return mega.cmdparse.ParameterParser # The main parser for the `:PluginTemplate arbitrary-thing` command. 8 | function M.make_parser() 9 | local parser = cmdparse.ParameterParser.new({ "arbitrary-thing", help = "Prepare to sleep or sleep." }) 10 | 11 | parser:add_parameter({ "-a", action = "store_true", help = "The -a flag." }) 12 | parser:add_parameter({ "-b", action = "store_true", help = "The -b flag." }) 13 | parser:add_parameter({ "-c", action = "store_true", help = "The -c flag." }) 14 | parser:add_parameter({ "-v", action = "store_true", count = "*", destination = "verbose", help = "The -v flag." }) 15 | parser:add_parameter({ "-f", action = "store_true", count = "*", help = "The -f flag." }) 16 | 17 | parser:set_execute(function(data) 18 | ---@cast data mega.cmdparse.NamespaceExecuteArguments 19 | local runner = require("plugin_template._commands.arbitrary_thing.runner") 20 | 21 | local names = {} 22 | 23 | for _, argument in ipairs(data.input.arguments) do 24 | table.insert(names, argument.name) 25 | end 26 | 27 | runner.run(names) 28 | end) 29 | 30 | return parser 31 | end 32 | 33 | return M 34 | -------------------------------------------------------------------------------- /lua/plugin_template/_commands/arbitrary_thing/runner.lua: -------------------------------------------------------------------------------- 1 | --- The main file that implements `arbitrary-thing` outside of COMMAND mode. 2 | 3 | local M = {} 4 | 5 | --- Print the `names`. 6 | --- 7 | ---@param names string[]? Some text to print out. e.g. `{"a", "b", "c"}`. 8 | --- 9 | function M.run(names) 10 | local text 11 | 12 | if not names or vim.tbl_isempty(names) then 13 | text = "" 14 | else 15 | text = vim.fn.join(names, ", ") 16 | end 17 | 18 | vim.notify(text, vim.log.levels.INFO) 19 | end 20 | 21 | return M 22 | -------------------------------------------------------------------------------- /lua/plugin_template/_commands/copy_logs/parser.lua: -------------------------------------------------------------------------------- 1 | --- The main parser for the `:PluginTemplate copy-logs` command. 2 | 3 | local cmdparse = require("mega.cmdparse") 4 | 5 | local M = {} 6 | 7 | ---@return mega.cmdparse.ParameterParser # The main parser for the `:PluginTemplate copy-logs` command. 8 | function M.make_parser() 9 | local parser = cmdparse.ParameterParser.new({ "copy-logs", help = "Get debug logs for PluginTemplate." }) 10 | 11 | parser:add_parameter({ 12 | "log", 13 | required = false, 14 | help = "The path on-disk to look for logs. If no path is given, a fallback log path is used instead.", 15 | }) 16 | 17 | parser:set_execute(function(data) 18 | ---@cast data mega.cmdparse.NamespaceExecuteArguments 19 | local runner = require("plugin_template._commands.copy_logs.runner") 20 | 21 | runner.run(data.namespace.log) 22 | end) 23 | 24 | return parser 25 | end 26 | 27 | return M 28 | -------------------------------------------------------------------------------- /lua/plugin_template/_commands/copy_logs/runner.lua: -------------------------------------------------------------------------------- 1 | --- The main file that implements `copy-logs` outside of COMMAND mode. 2 | 3 | local logging = require("mega.logging") 4 | 5 | local _LOGGER = logging.get_logger("plugin_template._commands.copy_logs.runner") 6 | 7 | local M = {} 8 | 9 | --- Modify the user's system clipboard with `result`. 10 | --- 11 | ---@param result plugin_template.ReadFileResult The file path + its contents that we read. 12 | --- 13 | local function _callback(result) 14 | vim.fn.setreg("+", result.data) 15 | 16 | vim.notify(string.format('Log file "%s" was copied to the clipboard.', result.path), vim.log.levels.INFO) 17 | end 18 | 19 | ---@class plugin_template.ReadFileResult 20 | --- A file path + its contents. 21 | ---@field data string 22 | --- The blob of text that was read from `path`. 23 | ---@field path string 24 | --- An absolute path to a file on-disk. 25 | 26 | --- Read the contents of `path` and pass its contents to `callback`. 27 | --- 28 | ---@param path string An absolute path to a file on-disk. 29 | ---@param callback fun(result: plugin_template.ReadFileResult): nil Call this once `path` is read. 30 | ---@private 31 | --- 32 | function M._read_file(path, callback) 33 | -- NOTE: mode 428 == rw-rw-rw- 34 | vim.uv.fs_open(path, "r", 438, function(error_open, handler) 35 | if error_open then 36 | error(error_open) 37 | end 38 | if not handler then 39 | error(string.format('Path "%s" could not be opened.', path)) 40 | end 41 | 42 | vim.uv.fs_fstat(handler, function(error_stat, stat) 43 | if error_stat then 44 | error(error_stat) 45 | end 46 | if not stat then 47 | error(string.format('Path "%s" could not be stat-ed.', path)) 48 | end 49 | 50 | vim.uv.fs_read(handler, stat.size, 0, function(error_read, data) 51 | if error_read then 52 | error(error_read) 53 | end 54 | if not data then 55 | error(string.format('Path "%s" could no be read for data.', path)) 56 | end 57 | 58 | vim.uv.fs_close(handler, function(error_close) 59 | assert(not error_close, error_close) 60 | 61 | return callback({ data = data, path = path }) 62 | end) 63 | end) 64 | end) 65 | end) 66 | end 67 | 68 | --- Copy the log data from the given `path` to the user's clipboard. 69 | --- 70 | ---@param path string? 71 | --- A path on-disk to look for logs. If none is given, the default fallback 72 | --- location is used instead. 73 | --- 74 | function M.run(path) 75 | path = path or _LOGGER:get_log_path() 76 | 77 | if not path or vim.fn.filereadable(path) ~= 1 then 78 | vim.notify(string.format('No "%s" path. Cannot copy the logs.', path), vim.log.levels.ERROR) 79 | 80 | return 81 | end 82 | 83 | local success, _ = pcall(M._read_file, path, vim.schedule_wrap(_callback)) 84 | 85 | if not success then 86 | vim.notify(string.format('Failed to read "%s" path. Cannot copy the logs.', path), vim.log.levels.ERROR) 87 | end 88 | end 89 | 90 | return M 91 | -------------------------------------------------------------------------------- /lua/plugin_template/_commands/goodnight_moon/count_sheep.lua: -------------------------------------------------------------------------------- 1 | --- The main file that implements `goodnight-moon count-sheep` outside of COMMAND mode. 2 | 3 | local configuration = require("plugin_template._core.configuration") 4 | local logging = require("mega.logging") 5 | 6 | local _LOGGER = logging.get_logger("plugin_template._commands.goodnight_moon.count_sheep") 7 | 8 | local M = {} 9 | 10 | --- Count a sheep for each `count`. 11 | --- 12 | ---@param count number Prints 1 sheep per `count`. A value that is 1-or-greater. 13 | --- 14 | function M.run(count) 15 | configuration.initialize_data_if_needed() 16 | _LOGGER:debug("Running goodnight-moon count-sheep") 17 | 18 | if count < 1 then 19 | _LOGGER:fmt_warning('Count "%s" cannot be less than 1. Using 1 instead.', count) 20 | 21 | count = 1 22 | end 23 | 24 | for index = 1, count do 25 | vim.notify(string.format("%s Sheep", index), vim.log.levels.INFO) 26 | end 27 | end 28 | 29 | return M 30 | -------------------------------------------------------------------------------- /lua/plugin_template/_commands/goodnight_moon/parser.lua: -------------------------------------------------------------------------------- 1 | --- The main parser for the `:PluginTemplate goodnight-moon` command. 2 | 3 | local cmdparse = require("mega.cmdparse") 4 | 5 | local M = {} 6 | 7 | ---@return mega.cmdparse.ParameterParser # The main parser for the `:PluginTemplate goodnight-moon` command. 8 | function M.make_parser() 9 | local parser = cmdparse.ParameterParser.new({ "goodnight-moon", help = "Prepare to sleep or sleep." }) 10 | local subparsers = 11 | parser:add_subparsers({ destination = "commands", help = "All goodnight-moon commands.", required = true }) 12 | 13 | local count_sheep = subparsers:add_parser({ "count-sheep", help = "Count some sheep to help you sleep." }) 14 | count_sheep:add_parameter({ "count", type = "number", help = "The number of sheept to count." }) 15 | local read = subparsers:add_parser({ "read", help = "Read a book in bed." }) 16 | read:add_parameter({ "book", help = "The name of the book to read." }) 17 | 18 | local sleep = subparsers:add_parser({ "sleep", help = "Sleep tight!" }) 19 | sleep:add_parameter({ 20 | "-z", 21 | action = "count", 22 | count = "*", 23 | destination = "count", 24 | help = "The number of Zzz to print.", 25 | }) 26 | 27 | count_sheep:set_execute(function(data) 28 | ---@cast data mega.cmdparse.NamespaceExecuteArguments 29 | local count_sheep_ = require("plugin_template._commands.goodnight_moon.count_sheep") 30 | 31 | count_sheep_.run(data.namespace.count) 32 | end) 33 | 34 | read:set_execute(function(data) 35 | ---@cast data mega.cmdparse.NamespaceExecuteArguments 36 | local read_ = require("plugin_template._commands.goodnight_moon.read") 37 | 38 | read_.run(data.namespace.book) 39 | end) 40 | 41 | sleep:set_execute(function(data) 42 | ---@cast data mega.cmdparse.NamespaceExecuteArguments 43 | local sleep_ = require("plugin_template._commands.goodnight_moon.sleep") 44 | 45 | sleep_.run(data.namespace.count) 46 | end) 47 | 48 | return parser 49 | end 50 | 51 | return M 52 | -------------------------------------------------------------------------------- /lua/plugin_template/_commands/goodnight_moon/read.lua: -------------------------------------------------------------------------------- 1 | --- The main file that implements `goodnight-moon read` outside of COMMAND mode. 2 | 3 | local logging = require("mega.logging") 4 | 5 | local _LOGGER = logging.get_logger("plugin_template._commands.goodnight_moon.read") 6 | 7 | local M = {} 8 | 9 | --- Print the name of the book. 10 | --- 11 | ---@param book string The name of the book. 12 | --- 13 | function M.run(book) 14 | _LOGGER:debug("Running goodnight-moon count-sheep") 15 | 16 | vim.notify(string.format("%s: it is a book", book), vim.log.levels.INFO) 17 | end 18 | 19 | return M 20 | -------------------------------------------------------------------------------- /lua/plugin_template/_commands/goodnight_moon/sleep.lua: -------------------------------------------------------------------------------- 1 | --- The main file that implements `goodnight-moon sleep` outside of COMMAND mode. 2 | 3 | local logging = require("mega.logging") 4 | 5 | local _LOGGER = logging.get_logger("plugin_template._commands.goodnight_moon.sleep") 6 | 7 | local M = {} 8 | 9 | --- Print Zzz each `count`. 10 | --- 11 | ---@param count number? Prints 1 Zzz per `count`. A value that is 1-or-greater. 12 | --- 13 | function M.run(count) 14 | _LOGGER:debug("Running goodnight-moon count-sheep") 15 | 16 | if count == nil then 17 | count = 1 18 | end 19 | 20 | if count < 1 then 21 | _LOGGER:fmt_warning('count-sheep "%s" is invalid. Setting the value to to 1-or-greater, instead.', count) 22 | 23 | count = 1 24 | end 25 | 26 | for _ = 1, count do 27 | vim.notify("Zzz", vim.log.levels.INFO) 28 | end 29 | end 30 | 31 | return M 32 | -------------------------------------------------------------------------------- /lua/plugin_template/_commands/hello_world/parser.lua: -------------------------------------------------------------------------------- 1 | --- The main parser for the `:PluginTemplate hello-world` command. 2 | 3 | local cmdparse = require("mega.cmdparse") 4 | local constant = require("plugin_template._commands.hello_world.say.constant") 5 | 6 | local M = {} 7 | 8 | --- Add the `--repeat` parameter onto `parser`. 9 | --- 10 | ---@param parser mega.cmdparse.ParameterParser The parent parser to add the parameter onto. 11 | --- 12 | local function _add_repeat_parameter(parser) 13 | parser:add_parameter({ 14 | names = { "--repeat", "-r" }, 15 | choices = function(data) 16 | --- @cast data mega.cmdparse.ChoiceData? 17 | 18 | local output = {} 19 | 20 | if not data or not data.current_value or data.current_value == "" then 21 | for index = 1, 5 do 22 | table.insert(output, tostring(index)) 23 | end 24 | 25 | return output 26 | end 27 | 28 | local value = tonumber(data.current_value) 29 | 30 | if not value then 31 | return {} 32 | end 33 | 34 | table.insert(output, tostring(value)) 35 | 36 | for index = 1, 4 do 37 | table.insert(output, tostring(value + index)) 38 | end 39 | 40 | return output 41 | end, 42 | default = 1, 43 | help = "Print to the user X number of times (default=1).", 44 | }) 45 | end 46 | 47 | --- Add the `--style` parameter onto `parser`. 48 | --- 49 | ---@param parser mega.cmdparse.ParameterParser The parent parser to add the parameter onto. 50 | --- 51 | local function _add_style_parameter(parser) 52 | parser:add_parameter({ 53 | names = { "--style", "-s" }, 54 | choices = { 55 | constant.Keyword.style.lowercase, 56 | constant.Keyword.style.uppercase, 57 | }, 58 | help = "lowercase makes WORD into word. uppercase does the reverse.", 59 | }) 60 | end 61 | 62 | ---@return mega.cmdparse.ParameterParser # The main parser for the `:PluginTemplate hello-world` command. 63 | function M.make_parser() 64 | local parser = cmdparse.ParameterParser.new({ "hello-world", help = "Print hello to the user." }) 65 | local top_subparsers = 66 | parser:add_subparsers({ destination = "commands", help = "All hello-world commands.", required = true }) 67 | --- @cast top_subparsers mega.cmdparse.Subparsers 68 | 69 | local say = top_subparsers:add_parser({ "say", help = "Print something to the user." }) 70 | local subparsers = 71 | say:add_subparsers({ destination = "say_commands", help = "All say-related commands.", required = true }) 72 | 73 | local phrase = subparsers:add_parser({ "phrase", help = "Print everything that the user types." }) 74 | phrase:add_parameter({ "phrases", count = "*", action = "append", help = "All of the text to print." }) 75 | _add_repeat_parameter(phrase) 76 | _add_style_parameter(phrase) 77 | 78 | local word = subparsers:add_parser({ "word", help = "Print only the first word that the user types." }) 79 | word:add_parameter({ "word", help = "The word to print." }) 80 | _add_repeat_parameter(word) 81 | _add_style_parameter(word) 82 | 83 | phrase:set_execute(function(data) 84 | ---@cast data mega.cmdparse.NamespaceExecuteArguments 85 | local runner = require("plugin_template._commands.hello_world.say.runner") 86 | 87 | local phrases = data.namespace.phrases 88 | 89 | if not phrases then 90 | phrases = {} 91 | end 92 | 93 | runner.run_say_phrase(phrases, data.namespace["repeat"], data.namespace.style) 94 | end) 95 | 96 | word:set_execute(function(data) 97 | ---@cast data mega.cmdparse.NamespaceExecuteArguments 98 | local runner = require("plugin_template._commands.hello_world.say.runner") 99 | 100 | runner.run_say_word(data.namespace.word or "", data.namespace["repeat"], data.namespace.style) 101 | end) 102 | 103 | return parser 104 | end 105 | 106 | return M 107 | -------------------------------------------------------------------------------- /lua/plugin_template/_commands/hello_world/say/constant.lua: -------------------------------------------------------------------------------- 1 | --- Symbolic variables to use for all `hello-world say`-related commands. 2 | 3 | return { 4 | Subcommand = { phrase = "phrase", word = "word" }, 5 | Keyword = { style = { lowercase = "lowercase", uppercase = "uppercase" } }, 6 | } 7 | -------------------------------------------------------------------------------- /lua/plugin_template/_commands/hello_world/say/runner.lua: -------------------------------------------------------------------------------- 1 | --- The main file that implements `hello-world say` outside of COMMAND mode. 2 | 3 | local constant = require("plugin_template._commands.hello_world.say.constant") 4 | local logging = require("mega.logging") 5 | 6 | local _LOGGER = logging.get_logger("plugin_template._commands.hello_world.say.runner") 7 | 8 | local M = {} 9 | 10 | --- Check if `text` is only whitespace. 11 | --- 12 | ---@param text string Some words / phrase to check. 13 | ---@return boolean # If `text` has only whitespace, return `true`. 14 | --- 15 | local function _is_whitespace(text) 16 | return text:match("^%s*$") == nil 17 | end 18 | 19 | --- Remove any phrases from `text` that has no meaningful words. 20 | --- 21 | ---@param text string[] All of the words to check. 22 | ---@return string[] # The non-empty text. 23 | --- 24 | local function _filter_missing_strings(text) 25 | local output = {} 26 | 27 | for _, phrase in ipairs(text) do 28 | if _is_whitespace(phrase) then 29 | table.insert(output, phrase) 30 | end 31 | end 32 | 33 | return output 34 | end 35 | 36 | --- Print `phrase` according to the other options. 37 | --- 38 | ---@param phrase string[] 39 | --- The text to say. 40 | ---@param repeat_? number 41 | --- A 1-or-more value. The number of times to print `word`. 42 | ---@param style? string 43 | --- Control how the text should be shown. 44 | --- 45 | local function _say(phrase, repeat_, style) 46 | repeat_ = repeat_ or 1 47 | style = style or constant.Keyword.style.lowercase 48 | local text = vim.fn.join(phrase, " ") 49 | 50 | if style == constant.Keyword.style.lowercase then 51 | text = string.lower(text) 52 | elseif style == constant.Keyword.style.uppercase then 53 | text = string.upper(text) 54 | end 55 | 56 | for _ = 1, repeat_ do 57 | vim.notify(text, vim.log.levels.INFO) 58 | end 59 | end 60 | 61 | --- Print `phrase` according to the other options. 62 | --- 63 | ---@param phrase string[] 64 | --- The text to say. 65 | ---@param repeat_ number? 66 | --- A 1-or-more value. The number of times to print `word`. 67 | ---@param style string? 68 | --- Control how the text should be shown. 69 | --- 70 | function M.run_say_phrase(phrase, repeat_, style) 71 | _LOGGER:debug("Running hello-world say word.") 72 | 73 | phrase = _filter_missing_strings(phrase) 74 | 75 | if vim.tbl_isempty(phrase) then 76 | vim.notify("No phrase was given", vim.log.levels.INFO) 77 | 78 | return 79 | end 80 | 81 | vim.notify("Saying phrase", vim.log.levels.INFO) 82 | 83 | _say(phrase, repeat_, style) 84 | end 85 | 86 | --- Print `phrase` according to the other options. 87 | --- 88 | ---@param word string 89 | --- The text to say. 90 | ---@param repeat_ number? 91 | --- A 1-or-more value. The number of times to print `word`. 92 | ---@param style string? 93 | --- Control how the text should be shown. 94 | --- 95 | function M.run_say_word(word, repeat_, style) 96 | _LOGGER:debug("Running hello-world say word.") 97 | 98 | if word == "" then 99 | vim.notify("No word was given", vim.log.levels.INFO) 100 | 101 | return 102 | end 103 | 104 | word = vim.fn.split(word, " ")[1] -- Make sure it's only one word 105 | 106 | vim.notify("Saying word", vim.log.levels.INFO) 107 | 108 | _say({ word }, repeat_, style) 109 | end 110 | 111 | return M 112 | -------------------------------------------------------------------------------- /lua/plugin_template/_core/configuration.lua: -------------------------------------------------------------------------------- 1 | --- All functions and data to help customize `plugin_template` for this user. 2 | 3 | local say_constant = require("plugin_template._commands.hello_world.say.constant") 4 | 5 | local logging = require("mega.logging") 6 | 7 | local _LOGGER = logging.get_logger("plugin_template._core.configuration") 8 | 9 | local M = {} 10 | 11 | -- NOTE: Don't remove this line. It makes the Lua module much easier to reload 12 | vim.g.loaded_plugin_template = false 13 | 14 | ---@type plugin_template.Configuration 15 | M.DATA = {} 16 | 17 | -- TODO: (you) If you use the mega.logging module for built-in logging, keep 18 | -- the `logging` section. Otherwise delete it. 19 | -- 20 | -- It's recommended to keep the `display` section in any case. 21 | -- 22 | ---@type plugin_template.Configuration 23 | local _DEFAULTS = { 24 | logging = { level = "info", use_console = false, use_file = false }, 25 | } 26 | 27 | -- TODO: (you) Update these sections depending on your intended plugin features. 28 | local _EXTRA_DEFAULTS = { 29 | commands = { 30 | goodnight_moon = { read = { phrase = "A good book" } }, 31 | hello_world = { 32 | say = { ["repeat"] = 1, style = say_constant.Keyword.style.lowercase }, 33 | }, 34 | }, 35 | tools = { 36 | lualine = { 37 | arbitrary_thing = { 38 | -- color = { link = "#555555" }, 39 | color = "Visual", 40 | text = " Arbitrary Thing", 41 | }, 42 | copy_logs = { 43 | -- color = { link = "#D3D3D3" }, 44 | color = "Comment", 45 | text = "󰈔 Copy Logs", 46 | }, 47 | goodnight_moon = { 48 | -- color = { fg = "#0000FF" }, 49 | color = "Question", 50 | text = "⏾ Goodnight moon", 51 | }, 52 | hello_world = { 53 | -- color = { fg = "#FFA07A" }, 54 | color = "Title", 55 | text = " Hello, World!", 56 | }, 57 | }, 58 | telescope = { 59 | goodnight_moon = { 60 | { "Foo Book", "Author A" }, 61 | { "Bar Book Title", "John Doe" }, 62 | { "Fizz Drink", "Some Name" }, 63 | { "Buzz Bee", "Cool Person" }, 64 | }, 65 | hello_world = { "Hi there!", "Hello, Sailor!", "What's up, doc?" }, 66 | }, 67 | }, 68 | } 69 | 70 | _DEFAULTS = vim.tbl_deep_extend("force", _DEFAULTS, _EXTRA_DEFAULTS) 71 | 72 | --- Setup `plugin_template` for the first time, if needed. 73 | function M.initialize_data_if_needed() 74 | if vim.g.loaded_plugin_template then 75 | return 76 | end 77 | 78 | M.DATA = vim.tbl_deep_extend("force", _DEFAULTS, vim.g.plugin_template_configuration or {}) 79 | 80 | vim.g.loaded_plugin_template = true 81 | 82 | local configuration = M.DATA.logging or {} 83 | ---@cast configuration mega.logging.SparseLoggerOptions 84 | logging.set_configuration("plugin_template", configuration) 85 | 86 | _LOGGER:fmt_debug("Initialized plugin-template's configuration.") 87 | end 88 | 89 | --- Merge `data` with the user's current configuration. 90 | --- 91 | ---@param data plugin_template.Configuration? All extra customizations for this plugin. 92 | ---@return plugin_template.Configuration # The configuration with 100% filled out values. 93 | --- 94 | function M.resolve_data(data) 95 | M.initialize_data_if_needed() 96 | 97 | return vim.tbl_deep_extend("force", M.DATA, data or {}) 98 | end 99 | 100 | return M 101 | -------------------------------------------------------------------------------- /lua/plugin_template/_core/tabler.lua: -------------------------------------------------------------------------------- 1 | --- Make dealing with Lua tables a bit easier. 2 | 3 | local M = {} 4 | 5 | --- Get a sub-section copy of `table_` as a new table. 6 | --- 7 | ---@param table_ table 8 | --- A list / array / dictionary / sequence to copy + reduce. 9 | ---@param first? number 10 | --- The start index to use. This value is **inclusive** (the given index 11 | --- will be returned). Uses `table_`'s first index if not provided. 12 | ---@param last? number 13 | --- The end index to use. This value is **inclusive** (the given index will 14 | --- be returned). Uses every index to the end of `table_`' if not provided. 15 | ---@param step? number 16 | --- The step size between elements in the slice. Defaults to 1 if not provided. 17 | ---@return table 18 | --- The subset of `table_`. 19 | --- 20 | function M.get_slice(table_, first, last, step) 21 | local sliced = {} 22 | 23 | for i = first or 1, last or #table_, step or 1 do 24 | sliced[#sliced + 1] = table_[i] 25 | end 26 | 27 | return sliced 28 | end 29 | 30 | --- Access the attribute(s) within `data` from `items`. 31 | --- 32 | ---@param data any Some nested data to query. e.g. `{a={b={c=true}}}`. 33 | ---@param items string[] Some attributes to query. e.g. `{"a", "b", "c"}`. 34 | ---@return any? # The found value, if any. 35 | --- 36 | function M.get_value(data, items) 37 | local current = data 38 | local found = {} 39 | local count = #items 40 | 41 | for index = 1, count do 42 | local item = items[index] 43 | current = current[item] 44 | 45 | if current == nil then 46 | return nil 47 | end 48 | 49 | table.insert(found, item) 50 | 51 | local type_ = type(current) 52 | 53 | if index < count and type_ ~= "table" then 54 | error(string.format("%s: expected table, got %s", vim.fn.join(found, "."), type_), 0) 55 | end 56 | end 57 | 58 | return current 59 | end 60 | 61 | --- Iterate over all of the given arrays. 62 | --- 63 | ---@param ... table[] All of the tables to expand 64 | ---@return any # Every element of each table, in order. 65 | --- 66 | function M.chain(...) 67 | local lists = { ... } 68 | local index = 0 69 | local current = 1 70 | 71 | return function() 72 | while current <= #lists do 73 | index = index + 1 74 | 75 | if index <= #lists[current] then 76 | return lists[current][index] 77 | else 78 | -- Move to the next list 79 | index = 0 80 | current = current + 1 81 | end 82 | end 83 | end 84 | end 85 | 86 | --- Delete the contents of `data`. 87 | --- 88 | ---@param data table A dictionary or array to clear. 89 | --- 90 | function M.clear(data) 91 | -- Clear the table 92 | for index = #data, 1, -1 do 93 | table.remove(data, index) 94 | end 95 | end 96 | 97 | --- Append all of `items` to `table_`. 98 | --- 99 | ---@param table_ any[] Any values to add. 100 | ---@param items any The values to add. 101 | --- 102 | function M.extend(table_, items) 103 | for _, item in ipairs(items) do 104 | table.insert(table_, item) 105 | end 106 | end 107 | 108 | --- Create a copy of `array` with its items in reverse order. 109 | --- 110 | ---@param array table Some (non-dictionary) items e.g. `{"a", "b", "c"}`. 111 | ---@return table # The reversed items e.g. `{"c", "b", "a"}`. 112 | --- 113 | function M.reverse_array(array) 114 | local output = {} 115 | 116 | for index = #array, 1, -1 do 117 | table.insert(output, array[index]) 118 | end 119 | 120 | return output 121 | end 122 | 123 | return M 124 | -------------------------------------------------------------------------------- /lua/plugin_template/_core/texter.lua: -------------------------------------------------------------------------------- 1 | --- Make manipulating Lua text easier. 2 | 3 | local M = {} 4 | 5 | --- Check if `character` is a standard A-Z 0-9ish character. 6 | --- 7 | ---@param character string Some single-value to check. 8 | ---@return boolean # If it's alpha return `true`. 9 | --- 10 | function M.is_alphanumeric(character) 11 | return character:match("^[A-Za-z0-9]$") ~= nil 12 | end 13 | 14 | --- Check if `character` is "regular" text but not alphanumeric. 15 | --- 16 | --- Examples would be Asian characters, Arabic, emojis, etc. 17 | --- 18 | ---@param character string Some single-value to check. 19 | ---@return boolean # If found return `true`. 20 | --- 21 | function M.is_unicode(character) 22 | local code_point = character:byte() 23 | 24 | return code_point > 127 25 | end 26 | 27 | --- Check if `items` is a flat array/list of string values. 28 | --- 29 | ---@param items any An array to check. 30 | ---@return boolean # If found, return `true`. 31 | --- 32 | function M.is_string_list(items) 33 | if type(items) ~= "table" then 34 | return false 35 | end 36 | 37 | for _, item in ipairs(items) do 38 | if type(item) ~= "string" then 39 | return false 40 | end 41 | end 42 | 43 | return true 44 | end 45 | 46 | --- Check if `character` is a space, tab, or newline. 47 | --- 48 | ---@param character string Basically `" "`, `\n`, `\t`. 49 | ---@return boolean # If it's any whitespace, return `true`. 50 | --- 51 | function M.is_whitespace(character) 52 | return character == "" or character:match("%s+") 53 | end 54 | 55 | --- Check all elements in `values` for `prefix` text. 56 | --- 57 | ---@param values string[] All values to check. e.g. `{"foo", "bar"}`. 58 | ---@param prefix string The prefix text to search for. 59 | ---@return string[] # All found values, if any. 60 | --- 61 | function M.get_array_startswith(values, prefix) 62 | local output = {} 63 | 64 | for _, value in ipairs(values) do 65 | if vim.startswith(value, prefix) then 66 | table.insert(output, value) 67 | end 68 | end 69 | 70 | return output 71 | end 72 | 73 | --- Add indentation to `text. 74 | --- 75 | ---@param text string Some phrase to indent one level. e.g. `"foo"`. 76 | ---@return string # The indented text, `" foo"`. 77 | --- 78 | function M.indent(text) 79 | return string.format(" %s", text) 80 | end 81 | 82 | --- Remove leading (left) whitespace `text`, if there is any. 83 | --- 84 | ---@param text string Some text e.g. `" -- "`. 85 | ---@return string # The removed text e.g. `"-- "`. 86 | --- 87 | function M.lstrip(text) 88 | return (text:gsub("^%s*", "")) 89 | end 90 | 91 | --- Check if `text` starts with `start` string. 92 | --- 93 | ---@param text string The full character / word / phrase. e.g. `"foot"`. 94 | ---@param start string The first letter(s) to check for. e.g.g `"foo"`. 95 | ---@return boolean # If found, return `true`. 96 | --- 97 | function M.startswith(text, start) 98 | return text:sub(1, #start) == start 99 | end 100 | 101 | return M 102 | -------------------------------------------------------------------------------- /lua/plugin_template/health.lua: -------------------------------------------------------------------------------- 1 | --- Make sure `plugin_template` will work as expected. 2 | --- 3 | --- At minimum, we validate that the user's configuration is correct. But other 4 | --- checks can happen here if needed. 5 | --- 6 | 7 | local configuration_ = require("plugin_template._core.configuration") 8 | local logging_ = require("mega.logging") 9 | local say_constant = require("plugin_template._commands.hello_world.say.constant") 10 | local tabler = require("plugin_template._core.tabler") 11 | local texter = require("plugin_template._core.texter") 12 | 13 | local _LOGGER = logging_.get_logger("plugin_template.health") 14 | 15 | local M = {} 16 | 17 | -- NOTE: This file is defer-loaded so it's okay to run this in the global scope 18 | configuration_.initialize_data_if_needed() 19 | 20 | ---@class lualine.ColorHex 21 | --- The table that Lualine expects when it sets colors. 22 | ---@field bg string 23 | --- The background hex color. e.g. `"#444444"`. 24 | ---@field fg string 25 | --- The text hex color. e.g. `"#DD0000"`. 26 | ---@field gui string 27 | --- The background hex color. e.g. `"#444444"`. 28 | 29 | --- Check if `value` has keys that it should not. 30 | --- 31 | ---@param value lualine.ColorHex 32 | --- 33 | local function _has_extra_color_keys(value) 34 | local keys = { "bg", "fg", "gui" } 35 | 36 | for key, _ in pairs(value) do 37 | if not vim.tbl_contains(keys, key) then 38 | return true 39 | end 40 | end 41 | 42 | return false 43 | end 44 | 45 | --- Make sure `text` is a HEX code. e.g. `"#D0FF1A"`. 46 | --- 47 | ---@param text string An expected HEX code. 48 | ---@return boolean # If `text` matches, return `true`. 49 | --- 50 | local function _is_hex_color(text) 51 | if type(text) ~= "string" then 52 | return false 53 | end 54 | 55 | return text:match("^#%x%x%x%x%x%x$") ~= nil 56 | end 57 | 58 | --- Add issues to `array` if there are errors. 59 | --- 60 | --- Todo: 61 | --- Once Neovim 0.10 is dropped, use the new function signature 62 | --- for vim.validate to make this function cleaner. 63 | --- 64 | ---@param array string[] 65 | --- All of the cumulated errors, if any. 66 | ---@param name string 67 | --- The key to check for. 68 | ---@param value_creator fun(): any 69 | --- A function that generates the value. 70 | ---@param expected string | fun(value: any): boolean 71 | --- If `value_creator()` does not match `expected`, this error message is 72 | --- shown to the user. 73 | ---@param message (string | boolean)? 74 | --- If it's a string, it's the error message when 75 | --- `value_creator()` does not match `expected`. When it's 76 | --- `true`, it means it's okay for `value_creator()` not to match `expected`. 77 | --- 78 | local function _append_validated(array, name, value_creator, expected, message) 79 | local success, value = pcall(value_creator) 80 | 81 | if not success then 82 | table.insert(array, value) 83 | 84 | return 85 | end 86 | 87 | local validated 88 | success, validated = pcall(vim.validate, { 89 | -- TODO: I think the Neovim type annotation is wrong. Once Neovim 90 | -- 0.10 is dropped let's just change this over to the new 91 | -- vim.validate signature. 92 | -- 93 | ---@diagnostic disable-next-line: assign-type-mismatch 94 | [name] = { value, expected, message }, 95 | }) 96 | 97 | if not success then 98 | table.insert(array, validated) 99 | end 100 | end 101 | 102 | --- Check if `data` is a boolean under `key`. 103 | --- 104 | ---@param key string The configuration value that we are checking. 105 | ---@param data any The object to validate. 106 | ---@return string? # The found error message, if any. 107 | --- 108 | local function _get_boolean_issue(key, data) 109 | local success, message = pcall(vim.validate, { 110 | [key] = { 111 | data, 112 | function(value) 113 | if value == nil then 114 | -- NOTE: This value is optional so it's fine it if is not defined. 115 | return true 116 | end 117 | 118 | return type(value) == "boolean" 119 | end, 120 | -- TODO: I think the Neovim type annotation is wrong. Once Neovim 121 | -- 0.10 is dropped let's just change this over to the new 122 | -- vim.validate signature. 123 | -- 124 | ---@diagnostic disable-next-line: assign-type-mismatch 125 | "a boolean", 126 | }, 127 | }) 128 | 129 | if success then 130 | return nil 131 | end 132 | 133 | return message 134 | end 135 | 136 | --- Check all "commands" values for issues. 137 | --- 138 | ---@param data plugin_template.Configuration All of the user's fallback settings. 139 | ---@return string[] # All found issues, if any. 140 | --- 141 | local function _get_command_issues(data) 142 | local output = {} 143 | 144 | _append_validated(output, "commands.goodnight_moon.read.phrase", function() 145 | return tabler.get_value(data, { "commands", "goodnight_moon", "read", "phrase" }) 146 | end, "string") 147 | 148 | _append_validated(output, "commands.hello_world.say.repeat", function() 149 | return tabler.get_value(data, { "commands", "hello_world", "say", "repeat" }) 150 | end, function(value) 151 | return type(value) == "number" and value > 0 152 | end, "a number (value must be 1-or-more)") 153 | 154 | _append_validated(output, "commands.hello_world.say.style", function() 155 | return tabler.get_value(data, { "commands", "hello_world", "say", "style" }) 156 | end, function(value) 157 | local choices = vim.tbl_keys(say_constant.Keyword.style) 158 | 159 | return vim.tbl_contains(choices, value) 160 | end, '"lowercase" or "uppercase"') 161 | 162 | return output 163 | end 164 | 165 | --- Check the contents of the "tools.lualine" configuration for any issues. 166 | --- 167 | --- Issues include: 168 | --- - Defining tools.lualine but it's not a table 169 | --- - Or the table, which is `table>`, has an incorrect value. 170 | --- - The inner tables must also follow a specific structure. 171 | --- 172 | ---@param command string A supported `plugin_template` command. e.g. `"hello_world"`. 173 | ---@return string[] # All found issues, if any. 174 | --- 175 | local function _get_lualine_command_issues(command, data) 176 | local output = {} 177 | 178 | _append_validated(output, string.format("tools.lualine.%s", command), function() 179 | return data 180 | end, function(value) 181 | if type(value) ~= "table" then 182 | return false 183 | end 184 | 185 | return true 186 | end, 'a table. e.g. { text="some text here" }') 187 | 188 | if not vim.tbl_isempty(output) then 189 | return output 190 | end 191 | 192 | _append_validated(output, string.format("tools.lualine.%s.text", command), function() 193 | return tabler.get_value(data, { "text" }) 194 | end, function(value) 195 | if type(value) ~= "string" then 196 | return false 197 | end 198 | 199 | return true 200 | end, 'a string. e.g. "some text here"') 201 | 202 | _append_validated(output, string.format("tools.lualine.%s.color", command), function() 203 | return tabler.get_value(data, { "color" }) 204 | end, function(value) 205 | if value == nil then 206 | -- NOTE: It's okay for this value to be undefined because 207 | -- we define a fallback for the user. 208 | -- 209 | return true 210 | end 211 | 212 | local type_ = type(value) 213 | 214 | if type_ == "string" then 215 | -- NOTE: We assume that there is a linkable highlight group 216 | -- with the name of `value` already or one that will exist. 217 | -- 218 | return true 219 | end 220 | 221 | if type_ == "table" then 222 | if value.bg ~= nil and not _is_hex_color(value.bg) then 223 | return false 224 | end 225 | 226 | if value.fg ~= nil and not _is_hex_color(value.fg) then 227 | return false 228 | end 229 | 230 | if value.gui ~= nil and type(value.gui) ~= "string" then 231 | return false 232 | end 233 | 234 | if _has_extra_color_keys(value) then 235 | return false 236 | end 237 | 238 | return true 239 | end 240 | 241 | return false 242 | end, 'a table. e.g. {fg="#000000", bg="#FFFFFF", gui="effect"}') 243 | 244 | return output 245 | end 246 | 247 | --- Check all "tools.lualine" values for issues. 248 | --- 249 | ---@param data plugin_template.Configuration All of the user's fallback settings. 250 | ---@return string[] # All found issues, if any. 251 | --- 252 | local function _get_lualine_issues(data) 253 | local output = {} 254 | 255 | local lualine = tabler.get_value(data, { "tools", "lualine" }) 256 | 257 | _append_validated(output, "tools.lualine", function() 258 | return lualine 259 | end, function(value) 260 | if type(value) ~= "table" then 261 | return false 262 | end 263 | 264 | return true 265 | end, "a table. e.g. { goodnight_moon = {...}, hello_world = {...} }") 266 | 267 | if not vim.tbl_isempty(output) then 268 | return output 269 | end 270 | 271 | for _, command in ipairs({ "arbitrary_thing", "goodnight_moon", "hello_world" }) do 272 | local value = tabler.get_value(lualine, { command }) 273 | 274 | -- NOTE: We have fallback values so it's okay if the value is nil. 275 | if value ~= nil then 276 | local issues = _get_lualine_command_issues(command, value) 277 | 278 | vim.list_extend(output, issues) 279 | end 280 | end 281 | 282 | return output 283 | end 284 | 285 | --- Check if logging configuration `data` has any issues. 286 | --- 287 | ---@param data plugin_template.LoggingConfiguration The user's logger settings. 288 | ---@return string[] # All of the found issues, if any. 289 | --- 290 | local function _get_logging_issues(data) 291 | local output = {} 292 | 293 | _append_validated(output, "logging", function() 294 | return data 295 | end, function(value) 296 | if type(value) ~= "table" then 297 | return false 298 | end 299 | 300 | return true 301 | end, 'a table. e.g. { level = "info", ... }') 302 | 303 | if not vim.tbl_isempty(output) then 304 | return output 305 | end 306 | 307 | _append_validated(output, "logging.level", function() 308 | return data.level 309 | end, function(value) 310 | if type(value) ~= "string" then 311 | return false 312 | end 313 | 314 | if not vim.tbl_contains({ "trace", "debug", "info", "warn", "error", "fatal" }, value) then 315 | return false 316 | end 317 | 318 | return true 319 | end, 'an enum. e.g. "trace" | "debug" | "info" | "warn" | "error" | "fatal"') 320 | 321 | local message = _get_boolean_issue("logging.use_console", data.use_console) 322 | 323 | if message ~= nil then 324 | table.insert(output, message) 325 | end 326 | 327 | message = _get_boolean_issue("logging.use_file", data.use_file) 328 | 329 | if message ~= nil then 330 | table.insert(output, message) 331 | end 332 | 333 | return output 334 | end 335 | 336 | --- Check all "tools.lualine" values for issues. 337 | --- 338 | ---@param data plugin_template.Configuration All of the user's fallback settings. 339 | ---@return string[] # All found issues, if any. 340 | --- 341 | local function _get_telescope_issues(data) 342 | local output = {} 343 | 344 | local telescope = tabler.get_value(data, { "tools", "telescope" }) 345 | 346 | _append_validated(output, "tools.telescope", function() 347 | return telescope 348 | end, function(value) 349 | if type(value) ~= "table" then 350 | return false 351 | end 352 | 353 | return true 354 | end, "a table. e.g. { goodnight_moon = {...}, hello_world = {...}}") 355 | 356 | if not vim.tbl_isempty(output) then 357 | return output 358 | end 359 | 360 | _append_validated(output, "tools.telescope.goodnight_moon", function() 361 | return telescope.goodnight_moon 362 | end, function(value) 363 | if value == nil then 364 | return true 365 | end 366 | 367 | if type(value) ~= "table" then 368 | return false 369 | end 370 | 371 | for _, item in ipairs(value) do 372 | if not texter.is_string_list(item) then 373 | return false 374 | end 375 | 376 | if #item ~= 2 then 377 | return false 378 | end 379 | end 380 | 381 | return true 382 | end, 'a table. e.g. { {"Book", "Author"} }') 383 | 384 | _append_validated(output, "tools.telescope.hello_world", function() 385 | return telescope.hello_world 386 | end, function(value) 387 | if value == nil then 388 | return true 389 | end 390 | 391 | if type(value) ~= "table" then 392 | return false 393 | end 394 | 395 | return texter.is_string_list(value) 396 | end, 'a table. e.g. { "Hello", "Hi", ...} }') 397 | 398 | return output 399 | end 400 | 401 | --- Check `data` for problems and return each of them. 402 | --- 403 | ---@param data plugin_template.Configuration? All extra customizations for this plugin. 404 | ---@return string[] # All found issues, if any. 405 | --- 406 | function M.get_issues(data) 407 | if not data or vim.tbl_isempty(data) then 408 | data = configuration_.resolve_data(vim.g.plugin_template_configuration) 409 | end 410 | 411 | local output = {} 412 | vim.list_extend(output, _get_command_issues(data)) 413 | 414 | local logging = data.logging 415 | 416 | if logging ~= nil then 417 | vim.list_extend(output, _get_logging_issues(data.logging)) 418 | end 419 | 420 | local lualine = tabler.get_value(data, { "tools", "lualine" }) 421 | 422 | if lualine ~= nil then 423 | vim.list_extend(output, _get_lualine_issues(data)) 424 | end 425 | 426 | local telescope = tabler.get_value(data, { "tools", "telescope" }) 427 | 428 | if telescope ~= nil then 429 | vim.list_extend(output, _get_telescope_issues(data)) 430 | end 431 | 432 | return output 433 | end 434 | 435 | --- Make sure `data` will work for `plugin_template`. 436 | --- 437 | ---@param data plugin_template.Configuration? All extra customizations for this plugin. 438 | --- 439 | function M.check(data) 440 | _LOGGER:debug("Running plugin-template health check.") 441 | 442 | vim.health.start("Configuration") 443 | 444 | local issues = M.get_issues(data) 445 | 446 | if vim.tbl_isempty(issues) then 447 | vim.health.ok("Your vim.g.plugin_template_configuration variable is great!") 448 | end 449 | 450 | for _, issue in ipairs(issues) do 451 | vim.health.error(issue) 452 | end 453 | end 454 | 455 | return M 456 | -------------------------------------------------------------------------------- /lua/plugin_template/init.lua: -------------------------------------------------------------------------------- 1 | --- All function(s) that can be called externally by other Lua modules. 2 | --- 3 | --- If a function's signature here changes in some incompatible way, this 4 | --- package must get a new **major** version. 5 | --- 6 | 7 | local configuration = require("plugin_template._core.configuration") 8 | local arbitrary_thing_runner = require("plugin_template._commands.arbitrary_thing.runner") 9 | local copy_logs_runner = require("plugin_template._commands.copy_logs.runner") 10 | local count_sheep = require("plugin_template._commands.goodnight_moon.count_sheep") 11 | local read = require("plugin_template._commands.goodnight_moon.read") 12 | local say_runner = require("plugin_template._commands.hello_world.say.runner") 13 | local sleep = require("plugin_template._commands.goodnight_moon.sleep") 14 | 15 | local M = {} 16 | 17 | configuration.initialize_data_if_needed() 18 | 19 | -- TODO: (you) - Change this file to whatever you need. These are just examples 20 | 21 | --- Print the `names`. 22 | --- 23 | ---@param names string[]? Some text to print out. e.g. `{"a", "b", "c"}`. 24 | --- 25 | function M.run_arbitrary_thing(names) 26 | arbitrary_thing_runner.run(names) 27 | end 28 | 29 | --- Copy the log data from the given `path` to the user's clipboard. 30 | --- 31 | ---@param path string? 32 | --- A path on-disk to look for logs. If none is given, the default fallback 33 | --- location is used instead. 34 | --- 35 | function M.run_copy_logs(path) 36 | copy_logs_runner.run(path) 37 | end 38 | 39 | --- Print `phrase` according to the other options. 40 | --- 41 | ---@param phrase string[] 42 | --- The text to say. 43 | ---@param repeat_ number? 44 | --- A 1-or-more value. The number of times to print `word`. 45 | ---@param style string? 46 | --- Control how the text should be shown. 47 | --- 48 | function M.run_hello_world_say_phrase(phrase, repeat_, style) 49 | say_runner.run_say_phrase(phrase, repeat_, style) 50 | end 51 | 52 | --- Print `phrase` according to the other options. 53 | --- 54 | ---@param word string 55 | --- The text to say. 56 | ---@param repeat_ number? 57 | --- A 1-or-more value. The number of times to print `word`. 58 | ---@param style string? 59 | --- Control how the text should be shown. 60 | --- 61 | function M.run_hello_world_say_word(word, repeat_, style) 62 | say_runner.run_say_word(word, repeat_, style) 63 | end 64 | 65 | --- Count a sheep for each `count`. 66 | --- 67 | ---@param count number Prints 1 sheep per `count`. A value that is 1-or-greater. 68 | --- 69 | function M.run_goodnight_moon_count_sheep(count) 70 | count_sheep.run(count) 71 | end 72 | 73 | --- Print the name of the book. 74 | --- 75 | ---@param book string The name of the book. 76 | --- 77 | function M.run_goodnight_moon_read(book) 78 | read.run(book) 79 | end 80 | 81 | --- Print Zzz each `count`. 82 | --- 83 | ---@param count number? Prints 1 Zzz per `count`. A value that is 1-or-greater. 84 | --- 85 | function M.run_goodnight_moon_sleep(count) 86 | sleep.run(count) 87 | end 88 | 89 | return M 90 | -------------------------------------------------------------------------------- /lua/plugin_template/types.lua: -------------------------------------------------------------------------------- 1 | --- A collection of types to be included / used in other Lua files. 2 | --- 3 | --- These types are either required by the Lua API or required for the normal 4 | --- operation of this Lua plugin. 5 | --- 6 | 7 | ---@class plugin_template.Configuration 8 | --- The user's customizations for this plugin. 9 | ---@field commands plugin_template.ConfigurationCommands? 10 | --- Customize the fallback behavior of all `:PluginTemplate` commands. 11 | ---@field logging plugin_template.LoggingConfiguration? 12 | --- Control how and which logs print to file / Neovim. 13 | ---@field tools plugin_template.ConfigurationTools? 14 | --- Optional third-party tool integrations. 15 | 16 | ---@class plugin_template.ConfigurationCommands 17 | --- Customize the fallback behavior of all `:PluginTemplate` commands. 18 | ---@field goodnight_moon plugin_template.ConfigurationGoodnightMoon? 19 | --- The default values when a user calls `:PluginTemplate goodnight-moon`. 20 | ---@field hello_world plugin_template.ConfigurationHelloWorld? 21 | --- The default values when a user calls `:PluginTemplate hello-world`. 22 | 23 | ---@class plugin_template.ConfigurationGoodnightMoon 24 | --- The default values when a user calls `:PluginTemplate goodnight-moon`. 25 | ---@field read plugin_template.ConfigurationGoodnightMoonRead? 26 | --- The default values when a user calls `:PluginTemplate goodnight-moon read`. 27 | 28 | ---@class plugin_template.LoggingConfiguration 29 | --- Control whether or not logging is printed to the console or to disk. 30 | ---@field level ( 31 | --- | "trace" 32 | --- | "debug" 33 | --- | "info" 34 | --- | "warn" | "error" 35 | --- | "fatal" 36 | --- | vim.log.levels.DEBUG 37 | --- | vim.log.levels.ERROR 38 | --- | vim.log.levels.INFO 39 | --- | vim.log.levels.TRACE 40 | --- | vim.log.levels.WARN)? 41 | --- Any messages above this level will be logged. 42 | ---@field use_console boolean? 43 | --- Should print the output to neovim while running. Warning: This is very 44 | --- spammy. You probably don't want to enable this unless you have to. 45 | ---@field use_file boolean? 46 | --- Should write to a file. 47 | ---@field output_path string? 48 | --- The default path on-disk where log files will be written to. 49 | --- Defaults to "/home/selecaoone/.local/share/nvim/plugin_name.log". 50 | 51 | ---@class plugin_template.ConfigurationGoodnightMoonRead 52 | --- The default values when a user calls `:PluginTemplate goodnight-moon read`. 53 | ---@field phrase string 54 | --- The book to read if no book is given by the user. 55 | 56 | ---@class plugin_template.ConfigurationHelloWorld 57 | --- The default values when a user calls `:PluginTemplate hello-world`. 58 | ---@field say plugin_template.ConfigurationHelloWorldSay? 59 | --- The default values when a user calls `:PluginTemplate hello-world say`. 60 | 61 | ---@class plugin_template.ConfigurationHelloWorldSay 62 | --- The default values when a user calls `:PluginTemplate hello-world say`. 63 | ---@field repeat number 64 | --- A 1-or-more value. When 1, the phrase is said once. When 2+, the phrase 65 | --- is repeated that many times. 66 | ---@field style "lowercase" | "uppercase" 67 | --- Control how the text is displayed. e.g. "uppercase" changes "hello" to "HELLO". 68 | 69 | ---@class plugin_template.ConfigurationTools 70 | --- Optional third-party tool integrations. 71 | ---@field lualine plugin_template.ConfigurationToolsLualine? 72 | --- A Vim statusline replacement that will show the command that the user just ran. 73 | 74 | ---@alias plugin_template.ConfigurationToolsLualine table 75 | --- Each runnable command and its display text. 76 | 77 | ---@class plugin_template.ConfigurationToolsLualineData 78 | --- The display values that will be used when a specific `plugin_template` 79 | --- command runs. 80 | ---@diagnostic disable-next-line: undefined-doc-name 81 | ---@field color vim.api.keyset.highlight? 82 | --- The foreground/background color to use for the Lualine status. 83 | ---@field prefix string? 84 | --- The text to display in lualine. 85 | -------------------------------------------------------------------------------- /lua/telescope/_extensions/plugin_template/init.lua: -------------------------------------------------------------------------------- 1 | --- Register `plugin_template` to telescope.nvim. 2 | --- 3 | ---@source https://github.com/nvim-telescope/telescope.nvim 4 | --- 5 | 6 | local has_telescope, telescope = pcall(require, "telescope") 7 | 8 | if not has_telescope then 9 | error("Telescope interface requires telescope.nvim (https://github.com/nvim-telescope/telescope.nvim)") 10 | end 11 | 12 | local configuration = require("plugin_template._core.configuration") 13 | local runner = require("telescope._extensions.plugin_template.runner") 14 | 15 | -- NOTE: This file is defer-loaded so it's okay to run this in the global scope 16 | configuration.initialize_data_if_needed() 17 | 18 | --- Run the `:Telescope plugin_template goodnight-moon` command. 19 | --- 20 | ---@param options telescope.CommandOptions The Telescope UI / layout options. 21 | --- 22 | local function _run_goodnight_moon(options) 23 | local picker = runner.get_goodnight_moon_picker(options) 24 | 25 | picker:find() 26 | end 27 | 28 | --- Run the `:Telescope plugin_template hello-world` command. 29 | --- 30 | ---@param options telescope.CommandOptions The Telescope UI / layout options. 31 | --- 32 | local function _run_hello_world(options) 33 | local picker = runner.get_hello_world_picker(options) 34 | 35 | picker:find() 36 | end 37 | 38 | return telescope.register_extension({ 39 | exports = { 40 | ["goodnight-moon"] = _run_goodnight_moon, 41 | ["hello-world"] = _run_hello_world, 42 | }, 43 | }) 44 | -------------------------------------------------------------------------------- /lua/telescope/_extensions/plugin_template/runner.lua: -------------------------------------------------------------------------------- 1 | --- Register `plugin_template` to telescope.nvim. 2 | --- 3 | ---@source https://github.com/nvim-telescope/telescope.nvim 4 | --- 5 | 6 | local M = {} 7 | 8 | local action_state = require("telescope.actions.state") 9 | local action_utils = require("telescope.actions.utils") 10 | local entry_display = require("telescope.pickers.entry_display") 11 | local finders = require("telescope.finders") 12 | local pickers = require("telescope.pickers") 13 | local telescope_actions = require("telescope.actions") 14 | local telescope_config = require("telescope.config").values 15 | 16 | local configuration = require("plugin_template._core.configuration") 17 | local read = require("plugin_template._commands.goodnight_moon.read") 18 | local say_runner = require("plugin_template._commands.hello_world.say.runner") 19 | local tabler = require("plugin_template._core.tabler") 20 | 21 | ---@diagnostic disable-next-line: deprecated 22 | local unpack = unpack or table.unpack 23 | 24 | vim.api.nvim_set_hl(0, "PluginTemplateTelescopeEntry", { link = "TelescopeResultsNormal", default = true }) 25 | vim.api.nvim_set_hl(0, "PluginTemplateTelescopeSecondary", { link = "TelescopeResultsComment", default = true }) 26 | 27 | ---@alias telescope.CommandOptions table 28 | 29 | --- Run the `:Telescope plugin_template goodnight-moon` command. 30 | --- 31 | ---@param options telescope.CommandOptions The Telescope UI / layout options. 32 | --- 33 | function M.get_goodnight_moon_picker(options) 34 | local function _select_book(buffer) 35 | for _, book in ipairs(M.get_selection(buffer)) do 36 | read.run(book) 37 | end 38 | 39 | telescope_actions.close(buffer) 40 | end 41 | 42 | local displayer = entry_display.create({ 43 | separator = " ", 44 | items = { 45 | { width = 0.8 }, 46 | { remaining = true }, 47 | }, 48 | }) 49 | 50 | local books = tabler.get_value(configuration.DATA, { "tools", "telescope", "goodnight_moon" }) or {} 51 | books = tabler.reverse_array(books) 52 | 53 | local picker = pickers.new(options, { 54 | prompt_title = "Choose A Book", 55 | finder = finders.new_table({ 56 | results = books, 57 | entry_maker = function(data) 58 | local name, author = unpack(data) 59 | local value = string.format("%s - %s", name, author) 60 | 61 | return { 62 | display = function(entry) 63 | return displayer({ 64 | { entry.name, "PluginTemplateTelescopeEntry" }, 65 | { entry.author, "PluginTemplateTelescopeSecondary" }, 66 | }) 67 | end, 68 | author = author, 69 | name = name, 70 | value = value, 71 | ordinal = value, 72 | } 73 | end, 74 | }), 75 | previewer = false, 76 | sorter = telescope_config.generic_sorter(options), 77 | attach_mappings = function() 78 | telescope_actions.select_default:replace(_select_book) 79 | 80 | return true 81 | end, 82 | }) 83 | 84 | return picker 85 | end 86 | 87 | --- Run the `:Telescope plugin_template hello-world` command. 88 | --- 89 | ---@param options telescope.CommandOptions The Telescope UI / layout options. 90 | --- 91 | function M.get_hello_world_picker(options) 92 | local function _select_phrases(buffer) 93 | local phrases = M.get_selection(buffer) 94 | 95 | say_runner.run_say_phrase(phrases) 96 | 97 | telescope_actions.close(buffer) 98 | end 99 | 100 | local displayer = entry_display.create({ 101 | separator = " ", 102 | items = { { width = 0.8 }, { remaining = true } }, 103 | }) 104 | 105 | local phrases = tabler.get_value(configuration.DATA, { "tools", "telescope", "hello_world" }) or {} 106 | phrases = tabler.reverse_array(phrases) 107 | 108 | local picker = pickers.new(options, { 109 | prompt_title = "Say Hello", 110 | finder = finders.new_table({ 111 | results = phrases, 112 | entry_maker = function(data) 113 | return { 114 | display = function(entry) 115 | return displayer({ entry.value }) 116 | end, 117 | name = data, 118 | value = data, 119 | ordinal = data, 120 | } 121 | end, 122 | }), 123 | previewer = false, 124 | sorter = telescope_config.generic_sorter(options), 125 | attach_mappings = function() 126 | telescope_actions.select_default:replace(_select_phrases) 127 | 128 | return true 129 | end, 130 | }) 131 | 132 | return picker 133 | end 134 | 135 | --- Gather the selected Telescope entries. 136 | --- 137 | --- If the user made selections, get each of those. If they pressed 138 | --- without any assignments then just get the line that they 139 | --- called on. 140 | --- 141 | ---@param buffer number A 0-or-more value of some Vim buffer. 142 | ---@return string[] # The found selection(s) if any. 143 | --- 144 | function M.get_selection(buffer) 145 | local books = {} 146 | 147 | action_utils.map_selections(buffer, function(selection) 148 | table.insert(books, selection.value) 149 | end) 150 | 151 | if not vim.tbl_isempty(books) then 152 | return books 153 | end 154 | 155 | local selection = action_state.get_selected_entry() 156 | 157 | if selection ~= nil then 158 | return { selection.value } 159 | end 160 | 161 | return {} 162 | end 163 | 164 | return M 165 | -------------------------------------------------------------------------------- /markdown/manual/.gitkeep: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ColinKennedy/nvim-best-practices-plugin-template/61aa9abef734619c00e117ed936a43281d99e128/markdown/manual/.gitkeep -------------------------------------------------------------------------------- /markdown/manual/docs/index.md: -------------------------------------------------------------------------------- 1 | This website was generated automatically using 2 | a fancy Lua -> Markdown -> HTML translator. See 3 | [nvim-best-practices-plugin-template](https://github.com/ColinKennedy/nvim-best-practice 4 | s-plugin-template) for details! 5 | -------------------------------------------------------------------------------- /plugin/plugin_template.lua: -------------------------------------------------------------------------------- 1 | --- All `plugin_template` command definitions. 2 | 3 | local cmdparse = require("mega.cmdparse") 4 | 5 | local _PREFIX = "PluginTemplate" 6 | 7 | ---@type mega.cmdparse.ParserCreator 8 | local _SUBCOMMANDS = function() 9 | local arbitrary_thing = require("plugin_template._commands.arbitrary_thing.parser") 10 | local copy_logs = require("plugin_template._commands.copy_logs.parser") 11 | local goodnight_moon = require("plugin_template._commands.goodnight_moon.parser") 12 | local hello_world = require("plugin_template._commands.hello_world.parser") 13 | 14 | local parser = cmdparse.ParameterParser.new({ name = _PREFIX, help = "The root of all commands." }) 15 | local subparsers = parser:add_subparsers({ "commands", help = "All runnable commands." }) 16 | 17 | subparsers:add_parser(arbitrary_thing.make_parser()) 18 | subparsers:add_parser(copy_logs.make_parser()) 19 | subparsers:add_parser(goodnight_moon.make_parser()) 20 | subparsers:add_parser(hello_world.make_parser()) 21 | 22 | return parser 23 | end 24 | 25 | cmdparse.create_user_command(_SUBCOMMANDS, _PREFIX) 26 | 27 | vim.keymap.set("n", "(PluginTemplateSayHi)", function() 28 | local configuration = require("plugin_template._core.configuration") 29 | local plugin_template = require("plugin_template") 30 | 31 | configuration.initialize_data_if_needed() 32 | 33 | plugin_template.run_hello_world_say_word("Hi!") 34 | end, { desc = "Say hi to the user." }) 35 | -------------------------------------------------------------------------------- /renovate.json: -------------------------------------------------------------------------------- 1 | { 2 | "$schema": "https://docs.renovatebot.com/renovate-schema.json", 3 | "extends": [ 4 | "config:recommended" 5 | ] 6 | } 7 | -------------------------------------------------------------------------------- /scripts/luacov.lua: -------------------------------------------------------------------------------- 1 | --- Run `busted` unittests with `LuaCov` enabled. 2 | --- 3 | --- If run correctly, this script will generate a `luacov.stats.out` file. 4 | --- 5 | --- e.g. `nvim -u NONE -U NONE -N -i NONE --headless -c "luafile scripts/luacov.lua" -c "quit"` 6 | --- 7 | 8 | local _CURRENT_DIRECTORY = vim.fn.fnamemodify(vim.fn.resolve(vim.fn.expand(":p")), ":h") 9 | 10 | local _PREVIOUS_RUN = vim.fs.joinpath(_CURRENT_DIRECTORY, "luacov.stats.out") 11 | 12 | if vim.fn.filereadable(_PREVIOUS_RUN) then 13 | os.remove(_PREVIOUS_RUN) 14 | end 15 | 16 | -- NOTE: LuaJIT is great but may interfere with coverage statistics 17 | -- so we need to turn it off. 18 | -- 19 | if jit then 20 | jit.off() 21 | end 22 | 23 | dofile("spec/minimal_init.lua") 24 | 25 | require("luacov") 26 | -- NOTE: These arguments were just found from experimentation. They 27 | -- may not be 100% right. 28 | _G.arg = { "--ignore-lua", [0] = "spec/minimal_init.lua" } 29 | require("busted.runner")({ standalone = false }) 30 | -------------------------------------------------------------------------------- /scripts/make_api_documentation/main.lua: -------------------------------------------------------------------------------- 1 | --- The file that auto-creates documentation for `plugin_template`. 2 | 3 | local vimdoc = require("mega.vimdoc") 4 | 5 | ---@return string # Get the directory on-disk where this Lua file is running from. 6 | local function _get_script_directory() 7 | local path = debug.getinfo(1, "S").source:sub(2) -- Remove the '@' at the start 8 | 9 | return path:match("(.*/)") 10 | end 11 | 12 | --- Convert the files in this plug-in from Lua docstrings to Vimdoc documentation. 13 | local function main() 14 | local current_directory = _get_script_directory() 15 | local root = vim.fs.normalize(vim.fs.joinpath(current_directory, "..", "..")) 16 | 17 | vimdoc.make_documentation_files({ 18 | { 19 | source = vim.fs.joinpath(root, "lua", "plugin_template", "init.lua"), 20 | destination = vim.fs.joinpath(root, "doc", "plugin_template_api.txt"), 21 | }, 22 | { 23 | source = vim.fs.joinpath(root, "lua", "plugin_template", "types.lua"), 24 | destination = vim.fs.joinpath(root, "doc", "plugin_template_types.txt"), 25 | }, 26 | }, { enable_module_in_signature = false }) 27 | end 28 | 29 | main() 30 | -------------------------------------------------------------------------------- /scripts/make_api_documentation/minimal_init.lua: -------------------------------------------------------------------------------- 1 | -- local root = vim.fn.fnamemodify("./.repro", ":p") 2 | local root = vim.fs.dirname(vim.fn.tempname()) 3 | 4 | -- set stdpaths to use `root` 5 | for _, name in ipairs({ "config", "data", "state", "cache" }) do 6 | vim.env[("XDG_%s_HOME"):format(name:upper())] = root .. "/" .. name 7 | end 8 | 9 | -- bootstrap lazy 10 | local lazypath = root .. "/plugins/lazy.nvim" 11 | if not vim.uv.fs_stat(lazypath) then 12 | vim.fn.system({ "git", "clone", "--filter=blob:none", "https://github.com/folke/lazy.nvim.git", lazypath }) 13 | end 14 | 15 | vim.opt.runtimepath:prepend(lazypath) 16 | 17 | local plugins = { 18 | load(vim.fn.system("curl -s https://raw.githubusercontent.com/ColinKennedy/mega.vimdoc/main/bootstrap.lua"))(), 19 | } 20 | 21 | require("lazy").setup(plugins, { 22 | root = root .. "/plugins", 23 | }) 24 | 25 | -- Attach this plugin so `mega.vimdoc` can see its Lua files later 26 | local current_directory = vim.fn.fnamemodify(vim.fn.resolve(vim.fn.expand(":p")), ":h") 27 | root = vim.fs.dirname(vim.fs.dirname(current_directory)) 28 | vim.o.runtimepath = root .. "," .. vim.o.runtimepath 29 | -------------------------------------------------------------------------------- /spec/minimal_init.lua: -------------------------------------------------------------------------------- 1 | --- Run the is file before you run unittests to download any extra dependencies. 2 | 3 | local _PLUGINS = { 4 | ["https://github.com/ColinKennedy/mega.cmdparse"] = os.getenv("MEGA_CMDPARSE_DIR") or "/tmp/mega.cmdparse", 5 | ["https://github.com/ColinKennedy/mega.logging"] = os.getenv("MEGA_LOGGING_DIR") or "/tmp/mega.logging", 6 | 7 | ["https://github.com/nvim-lualine/lualine.nvim"] = os.getenv("LUALINE_DIR") or "/tmp/lualine.nvim", 8 | 9 | ["https://github.com/nvim-telescope/telescope.nvim"] = os.getenv("TELESCOPE_DIR") or "/tmp/telescope.nvim", 10 | -- IMPORTANT: Needed to prevent error: 11 | -- `telescope.nvim/lua/telescope/config.lua:1: module 'plenary.strings' not found` 12 | -- 13 | ["https://github.com/nvim-lua/plenary.nvim"] = os.getenv("PLENARY_NVIM_DIR") or "/tmp/plenary.nvim", 14 | } 15 | 16 | local cloned = false 17 | 18 | for url, directory in pairs(_PLUGINS) do 19 | if vim.fn.isdirectory(directory) ~= 1 then 20 | print(string.format('Cloning "%s" plug-in to "%s" path.', url, directory)) 21 | 22 | vim.fn.system({ "git", "clone", url, directory }) 23 | 24 | cloned = true 25 | end 26 | 27 | vim.opt.rtp:append(directory) 28 | end 29 | 30 | if cloned then 31 | print("Finished cloning.") 32 | end 33 | 34 | vim.opt.rtp:append(".") 35 | 36 | vim.cmd("runtime plugin/plugin_template.lua") 37 | 38 | require("lualine").setup() 39 | 40 | require("plugin_template._core.configuration").initialize_data_if_needed() 41 | -------------------------------------------------------------------------------- /spec/plugin_template/configuration_spec.lua: -------------------------------------------------------------------------------- 1 | --- Make sure configuration health checks succeed or fail where they should. 2 | 3 | local configuration_ = require("plugin_template._core.configuration") 4 | local health = require("plugin_template.health") 5 | local tabler = require("plugin_template._core.tabler") 6 | 7 | local mock_vim = require("test_utilities.mock_vim") 8 | 9 | --- Make sure `data`, whether undefined, defined, or partially defined, is broken. 10 | --- 11 | ---@param data plugin_template.Configuration? The user customizations, if any. 12 | ---@param messages string[] All found, expected error messages. 13 | --- 14 | local function _assert_bad(data, messages) 15 | data = configuration_.resolve_data(data) 16 | local issues = health.get_issues(data) 17 | 18 | if vim.tbl_isempty(issues) then 19 | error(string.format('Test did not fail. Configuration "%s is valid.', vim.inspect(data))) 20 | 21 | return 22 | end 23 | 24 | assert.same(messages, issues) 25 | end 26 | 27 | --- Make sure `data`, whether undefined, defined, or partially defined, works. 28 | --- 29 | ---@param data plugin_template.Configuration? The user customizations, if any. 30 | --- 31 | local function _assert_good(data) 32 | data = configuration_.resolve_data(data) 33 | local issues = health.get_issues(data) 34 | 35 | if vim.tbl_isempty(issues) then 36 | return 37 | end 38 | 39 | error( 40 | string.format( 41 | 'Test did not succeed. Configuration "%s fails with "%s" issues.', 42 | vim.inspect(data), 43 | vim.inspect(issues) 44 | ) 45 | ) 46 | end 47 | 48 | describe("default", function() 49 | it("works with an #empty configuration", function() 50 | _assert_good({}) 51 | _assert_good() 52 | end) 53 | 54 | it("works with a fully defined, custom configuration", function() 55 | _assert_good({ 56 | commands = { 57 | goodnight_moon = { 58 | read = { phrase = "The Origin of Consciousness in the Breakdown of the Bicameral Mind" }, 59 | }, 60 | hello_world = { say = { ["repeat"] = 12, style = "uppercase" } }, 61 | }, 62 | }) 63 | end) 64 | 65 | it("works with the default configuration", function() 66 | _assert_good({ 67 | commands = { 68 | goodnight_moon = { phrase = "A good book" }, 69 | hello_world = { say = { ["repeat"] = 1, style = "lowercase" } }, 70 | }, 71 | }) 72 | end) 73 | 74 | it("works with the partially-defined configuration", function() 75 | _assert_good({ 76 | commands = { 77 | goodnight_moon = {}, 78 | hello_world = {}, 79 | }, 80 | }) 81 | end) 82 | end) 83 | 84 | ---@diagnostic disable: assign-type-mismatch 85 | ---@diagnostic disable: missing-fields 86 | describe("bad configuration - commands", function() 87 | it("happens with a bad type for #commands.goodnight_moon.phrase", function() 88 | _assert_bad( 89 | { commands = { goodnight_moon = { read = { phrase = 10 } } } }, 90 | { "commands.goodnight_moon.read.phrase: expected string, got number" } 91 | ) 92 | end) 93 | 94 | it("happens with a bad type for #commands.hello_world.say.repeat", function() 95 | _assert_bad( 96 | { commands = { hello_world = { say = { ["repeat"] = "foo" } } } }, 97 | { "commands.hello_world.say.repeat: expected a number (value must be 1-or-more), got foo" } 98 | ) 99 | end) 100 | 101 | it("happens with a bad value for #commands.hello_world.say.repeat", function() 102 | _assert_bad( 103 | { commands = { hello_world = { say = { ["repeat"] = -1 } } } }, 104 | { "commands.hello_world.say.repeat: expected a number (value must be 1-or-more), got -1" } 105 | ) 106 | end) 107 | 108 | it("happens with a bad type for #commands.hello_world.say.style", function() 109 | _assert_bad( 110 | { commands = { hello_world = { say = { style = 123 } } } }, 111 | { 'commands.hello_world.say.style: expected "lowercase" or "uppercase", got 123' } 112 | ) 113 | end) 114 | 115 | it("happens with a bad value for #commands.hello_world.say.style", function() 116 | _assert_bad( 117 | { commands = { hello_world = { say = { style = "bad_value" } } } }, 118 | { 'commands.hello_world.say.style: expected "lowercase" or "uppercase", got bad_value' } 119 | ) 120 | end) 121 | end) 122 | ---@diagnostic enable: assign-type-mismatch 123 | ---@diagnostic enable: missing-fields 124 | 125 | ---@diagnostic disable: assign-type-mismatch 126 | describe("bad configuration - logging", function() 127 | it("happens with a bad value for #logging", function() 128 | _assert_bad({ logging = false }, { 'logging: expected a table. e.g. { level = "info", ... }, got false' }) 129 | end) 130 | 131 | it("happens with a bad value for #logging.level", function() 132 | _assert_bad({ logging = { level = false } }, { 133 | "logging.level: expected an enum. " 134 | .. 'e.g. "trace" | "debug" | "info" | "warn" | "error" | "fatal", got false', 135 | }) 136 | 137 | _assert_bad({ logging = { level = "does not exist" } }, { 138 | "logging.level: expected an enum. " 139 | .. 'e.g. "trace" | "debug" | "info" | "warn" | "error" | "fatal", got does not exist', 140 | }) 141 | end) 142 | 143 | it("happens with a bad value for #logging.use_console", function() 144 | _assert_bad({ logging = { use_console = "aaa" } }, { "logging.use_console: expected a boolean, got aaa" }) 145 | end) 146 | 147 | it("happens with a bad value for #logging.use_file", function() 148 | _assert_bad({ logging = { use_file = "aaa" } }, { "logging.use_file: expected a boolean, got aaa" }) 149 | end) 150 | end) 151 | ---@diagnostic enable: assign-type-mismatch 152 | 153 | ---@diagnostic disable: assign-type-mismatch 154 | describe("health.check", function() 155 | before_each(function() 156 | mock_vim.mock_vim_health() 157 | end) 158 | after_each(mock_vim.reset_mocked_vim_health) 159 | 160 | it("works with an empty configuration", function() 161 | health.check({}) 162 | health.check() 163 | 164 | assert.same({}, mock_vim.get_vim_health_errors()) 165 | end) 166 | 167 | it("shows all issues at once", function() 168 | health.check({ 169 | commands = { 170 | goodnight_moon = { read = { phrase = 123 } }, 171 | hello_world = { say = { ["repeat"] = "aaa", style = 789 } }, 172 | }, 173 | logging = { 174 | level = false, 175 | use_console = "aaa", 176 | use_file = "fdas", 177 | }, 178 | tools = { 179 | lualine = { 180 | goodnight_moon = false, 181 | hello_world = { text = 456 }, 182 | }, 183 | }, 184 | }) 185 | 186 | local found = mock_vim.get_vim_health_errors() 187 | local issues = tabler.get_slice(found, 1, #found - 1) 188 | 189 | assert.same({ 190 | "commands.goodnight_moon.read.phrase: expected string, got number", 191 | "commands.hello_world.say.repeat: expected a number (value must be 1-or-more), got aaa", 192 | 'commands.hello_world.say.style: expected "lowercase" or "uppercase", got 789', 193 | 'logging.level: expected an enum. e.g. "trace" | "debug" | "info" | "warn" | "error" | "fatal", got false', 194 | "logging.use_console: expected a boolean, got aaa", 195 | "logging.use_file: expected a boolean, got fdas", 196 | 'tools.lualine.goodnight_moon: expected a table. e.g. { text="some text here" }, got false', 197 | }, issues) 198 | 199 | vim.startswith(found[#found], 'tools.lualine.hello_world.text: expected a string. e.g. "some text here", got ') 200 | end) 201 | end) 202 | ---@diagnostic enable: assign-type-mismatch 203 | -------------------------------------------------------------------------------- /spec/plugin_template/configuration_tools_lualine_spec.lua: -------------------------------------------------------------------------------- 1 | --- Make sure configuration health checks for lua succeed or fail where they should. 2 | 3 | local configuration_ = require("plugin_template._core.configuration") 4 | local health = require("plugin_template.health") 5 | 6 | ---@diagnostic disable: assign-type-mismatch 7 | 8 | --- Make sure `data`, whether undefined, defined, or partially defined, is broken. 9 | --- 10 | ---@param data plugin_template.Configuration? The user customizations, if any. 11 | ---@param messages string[] All found, expected error messages. 12 | --- 13 | local function _assert_bad(data, messages) 14 | data = configuration_.resolve_data(data) 15 | local issues = health.get_issues(data) 16 | 17 | if vim.tbl_isempty(issues) then 18 | error(string.format('Test did not fail. Configuration "%s is valid.', vim.inspect(data))) 19 | 20 | return 21 | end 22 | 23 | assert.same(messages, issues) 24 | end 25 | 26 | --- Make sure `data`, whether undefined, defined, or partially defined, works. 27 | --- 28 | ---@param data plugin_template.Configuration? The user customizations, if any. 29 | --- 30 | local function _assert_good(data) 31 | data = configuration_.resolve_data(data) 32 | local issues = health.get_issues(data) 33 | 34 | if vim.tbl_isempty(issues) then 35 | return 36 | end 37 | 38 | error( 39 | string.format( 40 | 'Test did not succeed. Configuration "%s fails with "%s" issues.', 41 | vim.inspect(data), 42 | vim.inspect(issues) 43 | ) 44 | ) 45 | end 46 | 47 | describe("bad configuration - tools.lualine", function() 48 | it("happens with a bad value for #tools.lualine.goodnight_moon", function() 49 | _assert_bad( 50 | { tools = { lualine = { goodnight_moon = true } } }, 51 | { 'tools.lualine.goodnight_moon: expected a table. e.g. { text="some text here" }, got true' } 52 | ) 53 | end) 54 | 55 | it("happens with a bad value for #tools.lualine.goodnight_moon.color", function() 56 | local data = configuration_.resolve_data({ tools = { lualine = { goodnight_moon = { color = false } } } }) 57 | local issues = health.get_issues(data) 58 | 59 | assert.equal(1, #issues) 60 | 61 | assert.is_truthy( 62 | vim.startswith( 63 | issues[1], 64 | "tools.lualine.goodnight_moon.color: expected a table. " 65 | .. 'e.g. {fg="#000000", bg="#FFFFFF", gui="effect"}' 66 | ) 67 | ) 68 | end) 69 | 70 | it("happens with a bad value for #tools.lualine.goodnight_moon.color - 002", function() 71 | local data = 72 | configuration_.resolve_data({ tools = { lualine = { goodnight_moon = { color = { bg = false } } } } }) 73 | local issues = health.get_issues(data) 74 | 75 | assert.equal(1, #issues) 76 | 77 | assert.is_truthy( 78 | vim.startswith( 79 | issues[1], 80 | "tools.lualine.goodnight_moon.color: expected a table. " 81 | .. 'e.g. {fg="#000000", bg="#FFFFFF", gui="effect"}' 82 | ) 83 | ) 84 | end) 85 | 86 | it("happens with a bad value for #tools.lualine.goodnight_moon.color - 003", function() 87 | local data = 88 | configuration_.resolve_data({ tools = { lualine = { goodnight_moon = { color = { fg = false } } } } }) 89 | local issues = health.get_issues(data) 90 | 91 | assert.equal(1, #issues) 92 | 93 | assert.is_truthy( 94 | vim.startswith( 95 | issues[1], 96 | "tools.lualine.goodnight_moon.color: expected a table. " 97 | .. 'e.g. {fg="#000000", bg="#FFFFFF", gui="effect"}' 98 | ) 99 | ) 100 | end) 101 | 102 | it("happens with a bad value for #tools.lualine.goodnight_moon.color - 004", function() 103 | local data = 104 | configuration_.resolve_data({ tools = { lualine = { goodnight_moon = { color = { gui = false } } } } }) 105 | local issues = health.get_issues(data) 106 | 107 | assert.equal(1, #issues) 108 | 109 | assert.is_truthy( 110 | vim.startswith( 111 | issues[1], 112 | "tools.lualine.goodnight_moon.color: expected a table. " 113 | .. 'e.g. {fg="#000000", bg="#FFFFFF", gui="effect"}' 114 | ) 115 | ) 116 | end) 117 | 118 | it("happens with a bad value for #tools.lualine.goodnight_moon.color - 005", function() 119 | local data = 120 | configuration_.resolve_data({ tools = { lualine = { goodnight_moon = { color = { bad_key = "ttt" } } } } }) 121 | local issues = health.get_issues(data) 122 | 123 | assert.equal(1, #issues) 124 | 125 | assert.is_truthy( 126 | vim.startswith( 127 | issues[1], 128 | "tools.lualine.goodnight_moon.color: expected a table. " 129 | .. 'e.g. {fg="#000000", bg="#FFFFFF", gui="effect"}' 130 | ) 131 | ) 132 | end) 133 | 134 | it("happens with a bad value for #tools.lualine.goodnight_moon.text", function() 135 | local data = configuration_.resolve_data({ tools = { lualine = { goodnight_moon = { text = false } } } }) 136 | local issues = health.get_issues(data) 137 | 138 | assert.equal(1, #issues) 139 | 140 | assert.is_truthy( 141 | vim.startswith( 142 | issues[1], 143 | 'tools.lualine.goodnight_moon.text: expected a string. e.g. "some text here", got ' 144 | ) 145 | ) 146 | end) 147 | 148 | it("happens with a bad value for #tools.lualine.hello_world", function() 149 | _assert_bad( 150 | { tools = { lualine = { hello_world = true } } }, 151 | { 'tools.lualine.hello_world: expected a table. e.g. { text="some text here" }, got true' } 152 | ) 153 | end) 154 | 155 | it("happens with a bad value for #tools.lualine.hello_world.color - 001", function() 156 | local data = configuration_.resolve_data({ tools = { lualine = { hello_world = { color = false } } } }) 157 | local issues = health.get_issues(data) 158 | 159 | assert.equal(1, #issues) 160 | 161 | assert.is_truthy( 162 | vim.startswith( 163 | issues[1], 164 | "tools.lualine.hello_world.color: expected a table. " 165 | .. 'e.g. {fg="#000000", bg="#FFFFFF", gui="effect"}' 166 | ) 167 | ) 168 | end) 169 | 170 | it("happens with a bad value for #tools.lualine.hello_world.color - 002", function() 171 | local data = configuration_.resolve_data({ tools = { lualine = { hello_world = { color = { bg = false } } } } }) 172 | local issues = health.get_issues(data) 173 | 174 | assert.equal(1, #issues) 175 | 176 | assert.is_truthy( 177 | vim.startswith( 178 | issues[1], 179 | "tools.lualine.hello_world.color: expected a table. " 180 | .. 'e.g. {fg="#000000", bg="#FFFFFF", gui="effect"}' 181 | ) 182 | ) 183 | end) 184 | 185 | it("happens with a bad value for #tools.lualine.hello_world.color - 003", function() 186 | local data = configuration_.resolve_data({ tools = { lualine = { hello_world = { color = { fg = false } } } } }) 187 | local issues = health.get_issues(data) 188 | 189 | assert.equal(1, #issues) 190 | 191 | assert.is_truthy( 192 | vim.startswith( 193 | issues[1], 194 | "tools.lualine.hello_world.color: expected a table. " 195 | .. 'e.g. {fg="#000000", bg="#FFFFFF", gui="effect"}' 196 | ) 197 | ) 198 | end) 199 | 200 | it("happens with a bad value for #tools.lualine.hello_world.color - 004", function() 201 | local data = 202 | configuration_.resolve_data({ tools = { lualine = { hello_world = { color = { gui = false } } } } }) 203 | local issues = health.get_issues(data) 204 | 205 | assert.equal(1, #issues) 206 | 207 | assert.is_truthy( 208 | vim.startswith( 209 | issues[1], 210 | "tools.lualine.hello_world.color: expected a table. " 211 | .. 'e.g. {fg="#000000", bg="#FFFFFF", gui="effect"}' 212 | ) 213 | ) 214 | end) 215 | 216 | it("happens with a bad value for #tools.lualine.hello_world.color - 005", function() 217 | local data = 218 | configuration_.resolve_data({ tools = { lualine = { hello_world = { color = { bad_key = "bbb" } } } } }) 219 | local issues = health.get_issues(data) 220 | 221 | assert.equal(1, #issues) 222 | 223 | assert.is_truthy( 224 | vim.startswith( 225 | issues[1], 226 | "tools.lualine.hello_world.color: expected a table. " 227 | .. 'e.g. {fg="#000000", bg="#FFFFFF", gui="effect"}' 228 | ) 229 | ) 230 | end) 231 | 232 | it("happens with a bad value for #tools.lualine.hello_world.text", function() 233 | local data = configuration_.resolve_data({ tools = { lualine = { hello_world = { text = false } } } }) 234 | local issues = health.get_issues(data) 235 | 236 | assert.equal(1, #issues) 237 | 238 | assert.is_truthy( 239 | vim.startswith( 240 | issues[1], 241 | "tools.lualine.hello_world.text: " .. 'expected a string. e.g. "some text here", got ' 242 | ) 243 | ) 244 | end) 245 | 246 | it("happens with a bad value for #tools.lualine", function() 247 | _assert_bad( 248 | { tools = { lualine = false } }, 249 | { "tools.lualine: expected a table. e.g. { goodnight_moon = {...}, hello_world = {...} }, got false" } 250 | ) 251 | end) 252 | end) 253 | 254 | describe("good configuration - tools.lualine", function() 255 | it("example good values", function() 256 | _assert_good({ 257 | tools = { 258 | lualine = { 259 | goodnight_moon = { text = "ttt" }, 260 | hello_world = { text = "yyyy" }, 261 | }, 262 | }, 263 | }) 264 | end) 265 | 266 | it("happens with a bad value for #tools.lualine.goodnight_moon.color", function() 267 | local data = configuration_.resolve_data({ tools = { lualine = { goodnight_moon = { color = false } } } }) 268 | local issues = health.get_issues(data) 269 | 270 | assert.equal(1, #issues) 271 | 272 | assert.is_truthy( 273 | vim.startswith( 274 | issues[1], 275 | "tools.lualine.goodnight_moon.color: expected a table. " 276 | .. 'e.g. {fg="#000000", bg="#FFFFFF", gui="effect"}' 277 | ) 278 | ) 279 | end) 280 | 281 | it("happens with a bad value for #tools.lualine.goodnight_moon.text", function() 282 | local data = configuration_.resolve_data({ tools = { lualine = { goodnight_moon = { text = false } } } }) 283 | local issues = health.get_issues(data) 284 | 285 | assert.equal(1, #issues) 286 | 287 | assert.is_truthy( 288 | vim.startswith( 289 | issues[1], 290 | 'tools.lualine.goodnight_moon.text: expected a string. e.g. "some text here", got ' 291 | ) 292 | ) 293 | end) 294 | 295 | it("happens with a bad value for #tools.lualine.hello_world", function() 296 | _assert_bad( 297 | { tools = { lualine = { hello_world = true } } }, 298 | { 'tools.lualine.hello_world: expected a table. e.g. { text="some text here" }, got true' } 299 | ) 300 | end) 301 | 302 | it("happens with a bad value for #tools.lualine.hello_world.color", function() 303 | local data = configuration_.resolve_data({ tools = { lualine = { hello_world = { color = false } } } }) 304 | local issues = health.get_issues(data) 305 | 306 | assert.equal(1, #issues) 307 | 308 | assert.is_truthy( 309 | vim.startswith( 310 | issues[1], 311 | "tools.lualine.hello_world.color: expected a table. " 312 | .. 'e.g. {fg="#000000", bg="#FFFFFF", gui="effect"}' 313 | ) 314 | ) 315 | end) 316 | 317 | it("happens with a bad value for #tools.lualine.hello_world.text", function() 318 | local data = configuration_.resolve_data({ tools = { lualine = { hello_world = { text = false } } } }) 319 | local issues = health.get_issues(data) 320 | 321 | assert.equal(1, #issues) 322 | 323 | assert.is_truthy( 324 | vim.startswith( 325 | issues[1], 326 | "tools.lualine.hello_world.text: " .. 'expected a string. e.g. "some text here", got ' 327 | ) 328 | ) 329 | end) 330 | 331 | it("happens with a bad value for #tools.lualine", function() 332 | _assert_bad( 333 | { tools = { lualine = false } }, 334 | { "tools.lualine: expected a table. e.g. { goodnight_moon = {...}, hello_world = {...} }, got false" } 335 | ) 336 | end) 337 | end) 338 | -------------------------------------------------------------------------------- /spec/plugin_template/configuration_tools_telescope_spec.lua: -------------------------------------------------------------------------------- 1 | --- Make sure configuration health checks for lua succeed or fail where they should. 2 | 3 | local configuration_ = require("plugin_template._core.configuration") 4 | local health = require("plugin_template.health") 5 | 6 | --- Make sure `data`, whether undefined, defined, or partially defined, is broken. 7 | --- 8 | ---@param data plugin_template.Configuration? The user customizations, if any. 9 | ---@param messages string[] All found, expected error messages. 10 | --- 11 | local function _assert_bad(data, messages) 12 | data = configuration_.resolve_data(data) 13 | local issues = health.get_issues(data) 14 | 15 | if vim.tbl_isempty(issues) then 16 | error(string.format('Test did not fail. Configuration "%s is valid.', vim.inspect(data))) 17 | 18 | return 19 | end 20 | 21 | assert.same(messages, issues) 22 | end 23 | 24 | --- Make sure `data`, whether undefined, defined, or partially defined, works. 25 | --- 26 | ---@param data plugin_template.Configuration? The user customizations, if any. 27 | --- 28 | local function _assert_good(data) 29 | data = configuration_.resolve_data(data) 30 | local issues = health.get_issues(data) 31 | 32 | if vim.tbl_isempty(issues) then 33 | return 34 | end 35 | 36 | error( 37 | string.format( 38 | 'Test did not succeed. Configuration "%s fails with "%s" issues.', 39 | vim.inspect(data), 40 | vim.inspect(issues) 41 | ) 42 | ) 43 | end 44 | 45 | describe("bad configuration - #tools.telescope", function() 46 | it("happens with a bad type for #tools.telescope", function() 47 | _assert_bad( 48 | { tools = { telescope = true } }, 49 | { "tools.telescope: expected a table. e.g. { goodnight_moon = {...}, hello_world = {...}}, got true" } 50 | ) 51 | end) 52 | 53 | it("happens with a bad type for #tools.telescope.goodnight_moon", function() 54 | _assert_bad( 55 | { tools = { telescope = { goodnight_moon = true } } }, 56 | { 'tools.telescope.goodnight_moon: expected a table. e.g. { {"Book", "Author"} }, got true' } 57 | ) 58 | end) 59 | 60 | it("happens with a bad value for #tools.telescope.goodnight_moon", function() 61 | local data = 62 | configuration_.resolve_data({ tools = { telescope = { goodnight_moon = { { true, "asdfasd" } } } } }) 63 | local issues = health.get_issues(data) 64 | 65 | assert.is_truthy( 66 | vim.startswith( 67 | issues[1], 68 | "tools.telescope.goodnight_moon: expected a table. " .. 'e.g. { {"Book", "Author"} }, ' 69 | ) 70 | ) 71 | end) 72 | 73 | it("happens with a bad type for #tools.telescope.hello_world", function() 74 | _assert_bad( 75 | { tools = { telescope = { hello_world = true } } }, 76 | { 'tools.telescope.hello_world: expected a table. e.g. { "Hello", "Hi", ...} }, got true' } 77 | ) 78 | end) 79 | 80 | it("happens with a bad value for #tools.telescope.hello_world", function() 81 | local data = configuration_.resolve_data({ tools = { telescope = { hello_world = { true } } } }) 82 | local issues = health.get_issues(data) 83 | 84 | assert.is_truthy( 85 | vim.startswith( 86 | issues[1], 87 | "tools.telescope.hello_world: expected a table. e.g. " .. '{ "Hello", "Hi", ...} }, ' 88 | ) 89 | ) 90 | end) 91 | end) 92 | 93 | describe("good configuration - #tools.telescope", function() 94 | it("works with a default #tools.telescope", function() 95 | _assert_good({ tools = { telescope = {} } }) 96 | end) 97 | 98 | it("works with an empty #tools.telescope.goodnight_moon", function() 99 | _assert_good({ tools = { telescope = { goodnight_moon = {} } } }) 100 | end) 101 | 102 | it("works with a valid #tools.telescope.goodnight_moon", function() 103 | _assert_good({ tools = { telescope = { goodnight_moon = { { "Foo", "Bar" } } } } }) 104 | end) 105 | 106 | it("works with an empty #tools.telescope.hello_world", function() 107 | _assert_good({ tools = { telescope = { hello_world = {} } } }) 108 | end) 109 | 110 | it("works with a valid #tools.telescope.hello_world", function() 111 | _assert_good({ tools = { telescope = { hello_world = { "Foo", "Bar" } } } }) 112 | end) 113 | end) 114 | -------------------------------------------------------------------------------- /spec/plugin_template/lualine_spec.lua: -------------------------------------------------------------------------------- 1 | --- Make that the Lualine component works as expected. 2 | 3 | local highlight = require("lualine.highlight") 4 | local loader = require("lualine.utils.loader") 5 | local lualine_plugin_template = require("lualine.components.plugin_template") 6 | local mock_test = require("test_utilities.mock_test") 7 | local plugin_template = require("plugin_template") 8 | 9 | ---@return table # The generated Lualine component. 10 | local function _make_component() 11 | return lualine_plugin_template({ self = { section = "y" } }) 12 | end 13 | 14 | --- Delete all existing highlight groups and recreate them (so we can keep tests clean). 15 | local function _refresh_highlight_groups() 16 | local theme = loader.load_theme("gruvbox") 17 | highlight.create_highlight_groups(theme) 18 | end 19 | 20 | --- Enable lualine so we can create lualine component(s) and other various tasks. 21 | local function _setup_lualine() 22 | lualine_plugin_template.PREVIOUS_COMMAND = nil 23 | 24 | mock_test.silence_all_internal_prints() 25 | 26 | _refresh_highlight_groups() 27 | end 28 | 29 | describe("default", function() 30 | before_each(_setup_lualine) 31 | after_each(mock_test.reset_mocked_vim_inspect) 32 | 33 | it("displays nothing if no command has been run yet", function() 34 | local component = _make_component() 35 | 36 | assert.is_nil(component:update_status()) 37 | end) 38 | end) 39 | 40 | describe("API calls", function() 41 | before_each(_setup_lualine) 42 | 43 | it("works with #arbitrary-thing", function() 44 | local component = _make_component() 45 | 46 | assert.is_nil(component:update_status()) 47 | 48 | plugin_template.run_arbitrary_thing() 49 | 50 | assert.equal("%#lualine_y_plugin_template_arbitrary_thing# Arbitrary Thing", component:update_status()) 51 | end) 52 | 53 | it("works with #copy-logs", function() 54 | local component = _make_component() 55 | 56 | assert.is_nil(component:update_status()) 57 | 58 | plugin_template.run_copy_logs() 59 | 60 | assert.equal("%#lualine_y_plugin_template_copy_logs#󰈔 Copy Logs", component:update_status()) 61 | end) 62 | 63 | it("works with #goodnight-moon #count-sheep", function() 64 | local component = _make_component() 65 | 66 | assert.is_nil(component:update_status()) 67 | 68 | plugin_template.run_goodnight_moon_count_sheep(10) 69 | 70 | assert.equal("%#lualine_y_plugin_template_goodnight_moon#⏾ Goodnight moon", component:update_status()) 71 | end) 72 | 73 | it("works with #goodnight-moon #read", function() 74 | local component = _make_component() 75 | 76 | assert.is_nil(component:update_status()) 77 | 78 | plugin_template.run_goodnight_moon_read("a book") 79 | 80 | assert.equal("%#lualine_y_plugin_template_goodnight_moon#⏾ Goodnight moon", component:update_status()) 81 | end) 82 | 83 | it("works with #goodnight-moon #sleep", function() 84 | local component = _make_component() 85 | 86 | assert.is_nil(component:update_status()) 87 | 88 | plugin_template.run_goodnight_moon_sleep() 89 | 90 | assert.equal("%#lualine_y_plugin_template_goodnight_moon#⏾ Goodnight moon", component:update_status()) 91 | end) 92 | 93 | it("works with #hello-world #say phrase", function() 94 | local component = _make_component() 95 | 96 | assert.is_nil(component:update_status()) 97 | 98 | plugin_template.run_hello_world_say_phrase({ "A phrase!" }) 99 | 100 | assert.equal("%#lualine_y_plugin_template_hello_world# Hello, World!", component:update_status()) 101 | end) 102 | 103 | it("works with #hello-world #say word", function() 104 | local component = _make_component() 105 | 106 | assert.is_nil(component:update_status()) 107 | 108 | plugin_template.run_hello_world_say_word("some_text_here") 109 | 110 | assert.equal("%#lualine_y_plugin_template_hello_world# Hello, World!", component:update_status()) 111 | end) 112 | end) 113 | 114 | describe("Command calls", function() 115 | before_each(_setup_lualine) 116 | 117 | it("works with #arbitrary-thing", function() 118 | local component = _make_component() 119 | 120 | assert.is_nil(component:update_status()) 121 | 122 | vim.cmd([[PluginTemplate arbitrary-thing]]) 123 | 124 | assert.equal("%#lualine_y_plugin_template_arbitrary_thing# Arbitrary Thing", component:update_status()) 125 | end) 126 | 127 | it("works with #copy-logs", function() 128 | local component = _make_component() 129 | 130 | assert.is_nil(component:update_status()) 131 | 132 | vim.cmd([[PluginTemplate copy-logs]]) 133 | 134 | assert.equal("%#lualine_y_plugin_template_copy_logs#󰈔 Copy Logs", component:update_status()) 135 | end) 136 | 137 | it("works with #goodnight-moon #count-sheep", function() 138 | local component = _make_component() 139 | 140 | assert.is_nil(component:update_status()) 141 | 142 | vim.cmd([[PluginTemplate goodnight-moon count-sheep 10]]) 143 | 144 | assert.equal("%#lualine_y_plugin_template_goodnight_moon#⏾ Goodnight moon", component:update_status()) 145 | end) 146 | 147 | it("works with #goodnight-moon #read", function() 148 | local component = _make_component() 149 | 150 | assert.is_nil(component:update_status()) 151 | 152 | vim.cmd([[PluginTemplate goodnight-moon read "a book"]]) 153 | 154 | assert.equal("%#lualine_y_plugin_template_goodnight_moon#⏾ Goodnight moon", component:update_status()) 155 | end) 156 | 157 | it("works with #goodnight-moon #sleep", function() 158 | local component = _make_component() 159 | 160 | assert.is_nil(component:update_status()) 161 | 162 | vim.cmd([[PluginTemplate goodnight-moon sleep -zzz]]) 163 | 164 | assert.equal("%#lualine_y_plugin_template_goodnight_moon#⏾ Goodnight moon", component:update_status()) 165 | end) 166 | 167 | it("works with #hello-world #say phrase", function() 168 | local component = _make_component() 169 | 170 | assert.is_nil(component:update_status()) 171 | 172 | vim.cmd([[PluginTemplate hello-world say phrase "something more text"]]) 173 | 174 | assert.equal("%#lualine_y_plugin_template_hello_world# Hello, World!", component:update_status()) 175 | end) 176 | 177 | it("works with #hello-world #say word", function() 178 | local component = _make_component() 179 | 180 | assert.is_nil(component:update_status()) 181 | 182 | vim.cmd([[PluginTemplate hello-world say word some_text_here]]) 183 | 184 | assert.equal("%#lualine_y_plugin_template_hello_world# Hello, World!", component:update_status()) 185 | end) 186 | end) 187 | -------------------------------------------------------------------------------- /spec/plugin_template/plugin_template_spec.lua: -------------------------------------------------------------------------------- 1 | --- Basic API tests. 2 | --- 3 | --- This module is pretty specific to this plugin template so you'll most 4 | --- likely want to delete or heavily modify this file. But it does give a quick 5 | --- look how to mock a test and some things you can do with Neovim/busted. 6 | 7 | local configuration = require("plugin_template._core.configuration") 8 | local copy_logs_runner = require("plugin_template._commands.copy_logs.runner") 9 | local logging = require("mega.logging") 10 | local plugin_template = require("plugin_template") 11 | 12 | ---@class plugin_template.Configuration 13 | local _CONFIGURATION_DATA 14 | 15 | ---@type string[] 16 | local _DATA = {} 17 | 18 | local _ORIGINAL_COPY_LOGS_READ_FILE = copy_logs_runner._read_file 19 | local _ORIGINAL_NOTIFY = vim.notify 20 | 21 | --- Keep track of text that would have been printed. Save it to a variable instead. 22 | --- 23 | ---@param data string Some text to print to stdout. 24 | --- 25 | local function _save_prints(data) 26 | table.insert(_DATA, data) 27 | end 28 | 29 | --- Mock all functions / states before a unittest runs (call this before each test). 30 | local function _initialize_prints() 31 | vim.notify = _save_prints 32 | end 33 | 34 | --- Watch the `copy-logs` API command for function calls. 35 | local function _initialize_copy_log() 36 | local function _save_path(path) 37 | _DATA = { path } 38 | end 39 | 40 | _CONFIGURATION_DATA = vim.deepcopy(configuration.DATA) 41 | copy_logs_runner._read_file = _save_path 42 | end 43 | 44 | --- Write a log file so we can query its later later. 45 | local function _make_fake_log(path) 46 | configuration.DATA.logging.output_path = path 47 | local logging_configuration = configuration.DATA.logging or {} 48 | ---@cast logging_configuration mega.logging.SparseLoggerOptions 49 | logging.set_configuration("plugin_template", logging_configuration) 50 | 51 | local file = io.open(path, "w") -- Open the file in write mode 52 | 53 | if not file then 54 | error(string.format('Path "%s" is not writable.', path)) 55 | end 56 | 57 | file:write("aaa\nbbb\nccc\n") 58 | file:close() 59 | end 60 | 61 | --- Remove the "watcher" that we added during unittesting. 62 | local function _reset_copy_log() 63 | copy_logs_runner._read_file = _ORIGINAL_COPY_LOGS_READ_FILE 64 | 65 | configuration.DATA = _CONFIGURATION_DATA 66 | _DATA = {} 67 | end 68 | 69 | --- Reset all functions / states to their previous settings before the test had run. 70 | local function _reset_prints() 71 | vim.notify = _ORIGINAL_NOTIFY 72 | _DATA = {} 73 | end 74 | 75 | --- Wait for our (mocked) unittest variable to get some data back. 76 | --- 77 | ---@param timeout number? 78 | --- The milliseconds to wait before continuing. If the timeout is exceeded 79 | --- then we stop waiting for all of the functions to call. 80 | --- 81 | local function _wait_for_result(timeout) 82 | if timeout == nil then 83 | timeout = 1000 84 | end 85 | 86 | vim.wait(timeout, function() 87 | return not vim.tbl_isempty(_DATA) 88 | end) 89 | end 90 | 91 | describe("arbitrary-thing API", function() 92 | before_each(_initialize_prints) 93 | after_each(_reset_prints) 94 | 95 | it("runs #arbitrary-thing with #default arguments", function() 96 | plugin_template.run_arbitrary_thing({}) 97 | 98 | assert.same({ "" }, _DATA) 99 | end) 100 | 101 | it("runs #arbitrary-thing with arguments", function() 102 | plugin_template.run_arbitrary_thing({ "v", "t" }) 103 | 104 | assert.same({ "v, t" }, _DATA) 105 | end) 106 | end) 107 | 108 | describe("arbitrary-thing commands", function() 109 | before_each(_initialize_prints) 110 | after_each(_reset_prints) 111 | 112 | it("runs #arbitrary-thing with #default arguments", function() 113 | vim.cmd([[PluginTemplate arbitrary-thing]]) 114 | assert.same({ "" }, _DATA) 115 | end) 116 | 117 | it("runs #arbitrary-thing with arguments", function() 118 | vim.cmd([[PluginTemplate arbitrary-thing -vvv -abc -f]]) 119 | 120 | assert.same({ "-v, -v, -v, -a, -b, -c, -f" }, _DATA) 121 | end) 122 | end) 123 | 124 | describe("copy logs API", function() 125 | before_each(_initialize_copy_log) 126 | after_each(_reset_copy_log) 127 | 128 | it("runs with an explicit file path", function() 129 | local path = vim.fn.tempname() .. "copy_logs_test.log" 130 | _make_fake_log(path) 131 | 132 | plugin_template.run_copy_logs(path) 133 | _wait_for_result() 134 | 135 | assert.same({ path }, _DATA) 136 | end) 137 | 138 | it("runs with default arguments", function() 139 | local expected = vim.fn.tempname() .. "_copy_logs_default_test.log" 140 | _make_fake_log(expected) 141 | 142 | plugin_template.run_copy_logs() 143 | _wait_for_result() 144 | 145 | assert.same({ expected }, _DATA) 146 | end) 147 | end) 148 | 149 | describe("copy logs command", function() 150 | before_each(_initialize_copy_log) 151 | after_each(_reset_copy_log) 152 | 153 | it("runs with an explicit file path", function() 154 | local expected = vim.fn.tempname() .. "_copy_logs_explicit_file_path_test.log" 155 | _make_fake_log(expected) 156 | 157 | vim.cmd(string.format('PluginTemplate copy-logs "%s"', expected)) 158 | _wait_for_result() 159 | 160 | assert.same({ expected }, _DATA) 161 | end) 162 | 163 | it("runs with default arguments", function() 164 | local expected = vim.fn.tempname() .. "_copy_logs_default_arguments_test.log" 165 | _make_fake_log(expected) 166 | 167 | vim.cmd([[PluginTemplate copy-logs]]) 168 | 169 | _wait_for_result() 170 | 171 | assert.same({ expected }, _DATA) 172 | end) 173 | end) 174 | 175 | describe("hello world API - say phrase/word", function() 176 | before_each(_initialize_prints) 177 | after_each(_reset_prints) 178 | 179 | it("runs #hello-world with default `say phrase` arguments - 001", function() 180 | plugin_template.run_hello_world_say_phrase({ "" }) 181 | 182 | assert.same({ "No phrase was given" }, _DATA) 183 | end) 184 | 185 | it("runs #hello-world with default `say phrase` arguments - 002", function() 186 | plugin_template.run_hello_world_say_phrase({}) 187 | 188 | assert.same({ "No phrase was given" }, _DATA) 189 | end) 190 | 191 | it("runs #hello-world with default `say word` arguments - 001", function() 192 | plugin_template.run_hello_world_say_word("") 193 | 194 | assert.same({ "No word was given" }, _DATA) 195 | end) 196 | 197 | it("runs #hello-world say phrase - with all of its arguments", function() 198 | plugin_template.run_hello_world_say_phrase({ "Hello,", "World!" }, 2, "lowercase") 199 | 200 | assert.same({ "Saying phrase", "hello, world!", "hello, world!" }, _DATA) 201 | end) 202 | 203 | it("runs #hello-world say word - with all of its arguments", function() 204 | plugin_template.run_hello_world_say_phrase({ "Hi" }, 2, "uppercase") 205 | 206 | assert.same({ "Saying phrase", "HI", "HI" }, _DATA) 207 | end) 208 | end) 209 | 210 | describe("hello world commands - say phrase/word", function() 211 | before_each(_initialize_prints) 212 | after_each(_reset_prints) 213 | 214 | it("runs #hello-world with default arguments", function() 215 | vim.cmd([[PluginTemplate hello-world say phrase]]) 216 | 217 | assert.same({ "No phrase was given" }, _DATA) 218 | end) 219 | 220 | it("runs #hello-world say phrase - with all of its arguments", function() 221 | vim.cmd([[PluginTemplate hello-world say phrase "Hello, World!" --repeat=2 --style=lowercase]]) 222 | 223 | assert.same({ "Saying phrase", "hello, world!", "hello, world!" }, _DATA) 224 | end) 225 | 226 | it("runs #hello-world say word - with all of its arguments", function() 227 | vim.cmd([[PluginTemplate hello-world say word "Hi" --repeat=2 --style=uppercase]]) 228 | 229 | assert.same({ "Saying word", "HI", "HI" }, _DATA) 230 | end) 231 | end) 232 | 233 | describe("goodnight-moon API", function() 234 | before_each(_initialize_prints) 235 | after_each(_reset_prints) 236 | 237 | it("runs #goodnight-moon #count-sheep with all of its arguments", function() 238 | plugin_template.run_goodnight_moon_count_sheep(3) 239 | 240 | assert.same({ "1 Sheep", "2 Sheep", "3 Sheep" }, _DATA) 241 | end) 242 | 243 | it("runs #goodnight-moon #read with all of its arguments", function() 244 | plugin_template.run_goodnight_moon_read("a good book") 245 | 246 | assert.same({ "a good book: it is a book" }, _DATA) 247 | end) 248 | 249 | it("runs #goodnight-moon #sleep with all of its arguments", function() 250 | plugin_template.run_goodnight_moon_sleep(3) 251 | 252 | assert.same({ "Zzz", "Zzz", "Zzz" }, _DATA) 253 | end) 254 | end) 255 | 256 | describe("goodnight-moon commands", function() 257 | before_each(_initialize_prints) 258 | after_each(_reset_prints) 259 | 260 | it("runs #goodnight-moon #count-sheep with all of its arguments", function() 261 | vim.cmd([[PluginTemplate goodnight-moon count-sheep 3]]) 262 | 263 | assert.same({ "1 Sheep", "2 Sheep", "3 Sheep" }, _DATA) 264 | end) 265 | 266 | it("runs #goodnight-moon #read with all of its arguments", function() 267 | vim.cmd([[PluginTemplate goodnight-moon read "a good book"]]) 268 | 269 | assert.same({ "a good book: it is a book" }, _DATA) 270 | end) 271 | 272 | it("runs #goodnight-moon #sleep with all of its arguments", function() 273 | vim.cmd([[PluginTemplate goodnight-moon sleep -z -z -z]]) 274 | 275 | assert.same({ "Zzz", "Zzz", "Zzz" }, _DATA) 276 | end) 277 | end) 278 | 279 | describe("help API", function() 280 | before_each(_initialize_prints) 281 | after_each(_reset_prints) 282 | 283 | describe("fallback help", function() 284 | it("works on a nested subparser - 003", function() 285 | vim.cmd("PluginTemplate hello-world say") 286 | assert.same({ "Usage: {say} {phrase,word} [--help]" }, _DATA) 287 | end) 288 | end) 289 | 290 | describe("--help flag", function() 291 | it("works on the base parser", function() 292 | vim.cmd("PluginTemplate --help") 293 | 294 | assert.same({ 295 | [[ 296 | Usage: PluginTemplate {arbitrary-thing,copy-logs,goodnight-moon,hello-world} [--help] 297 | 298 | Commands: 299 | arbitrary-thing Prepare to sleep or sleep. 300 | copy-logs Get debug logs for PluginTemplate. 301 | goodnight-moon Prepare to sleep or sleep. 302 | hello-world Print hello to the user. 303 | 304 | Options: 305 | --help -h Show this help message and exit. 306 | ]], 307 | }, _DATA) 308 | end) 309 | 310 | it("works on a nested subparser - 001", function() 311 | vim.cmd([[PluginTemplate hello-world say --help]]) 312 | 313 | assert.same({ 314 | [[ 315 | Usage: {say} {phrase,word} [--help] 316 | 317 | Commands: 318 | phrase Print everything that the user types. 319 | word Print only the first word that the user types. 320 | 321 | Options: 322 | --help -h Show this help message and exit. 323 | ]], 324 | }, _DATA) 325 | end) 326 | 327 | it("works on a nested subparser - 002", function() 328 | vim.cmd([[PluginTemplate hello-world say phrase --help]]) 329 | 330 | assert.same({ 331 | [[ 332 | Usage: {phrase} PHRASES* [--repeat {1,2,3,4,5}] [--style {lowercase,uppercase}] [--help] 333 | 334 | Positional Arguments: 335 | PHRASES* All of the text to print. 336 | 337 | Options: 338 | --repeat -r {1,2,3,4,5} Print to the user X number of times (default=1). 339 | --style -s {lowercase,uppercase} lowercase makes WORD into word. uppercase does the reverse. 340 | --help -h Show this help message and exit. 341 | ]], 342 | }, _DATA) 343 | end) 344 | 345 | it("works on a nested subparser - 003", function() 346 | vim.cmd([[PluginTemplate hello-world say word --help]]) 347 | 348 | assert.same({ 349 | [[ 350 | Usage: {word} WORD [--repeat {1,2,3,4,5}] [--style {lowercase,uppercase}] [--help] 351 | 352 | Positional Arguments: 353 | WORD The word to print. 354 | 355 | Options: 356 | --repeat -r {1,2,3,4,5} Print to the user X number of times (default=1). 357 | --style -s {lowercase,uppercase} lowercase makes WORD into word. uppercase does the reverse. 358 | --help -h Show this help message and exit. 359 | ]], 360 | }, _DATA) 361 | end) 362 | 363 | it("works on the subparsers - 001", function() 364 | vim.cmd([[PluginTemplate arbitrary-thing --help]]) 365 | 366 | assert.same({ 367 | [[ 368 | Usage: {arbitrary-thing} [-a] [-b] [-c] [-f] [-v] [--help] 369 | 370 | Options: 371 | -a The -a flag. 372 | -b The -b flag. 373 | -c The -c flag. 374 | -f * The -f flag. 375 | -v * The -v flag. 376 | --help -h Show this help message and exit. 377 | ]], 378 | }, _DATA) 379 | end) 380 | 381 | it("works on the subparsers - 002", function() 382 | vim.cmd([[PluginTemplate copy-logs --help]]) 383 | 384 | assert.same({ 385 | [[ 386 | Usage: {copy-logs} LOG [--help] 387 | 388 | Positional Arguments: 389 | LOG The path on-disk to look for logs. If no path is given, a fallback log path is used instead. 390 | 391 | Options: 392 | --help -h Show this help message and exit. 393 | ]], 394 | }, _DATA) 395 | end) 396 | 397 | it("works on the subparsers - 003", function() 398 | vim.cmd([[PluginTemplate goodnight-moon --help]]) 399 | 400 | assert.same({ 401 | [[ 402 | Usage: {goodnight-moon} {count-sheep,read,sleep} [--help] 403 | 404 | Commands: 405 | count-sheep Count some sheep to help you sleep. 406 | read Read a book in bed. 407 | sleep Sleep tight! 408 | 409 | Options: 410 | --help -h Show this help message and exit. 411 | ]], 412 | }, _DATA) 413 | end) 414 | 415 | it("works on the subparsers - 004", function() 416 | vim.cmd([[PluginTemplate hello-world --help]]) 417 | 418 | assert.same({ 419 | [[ 420 | Usage: {hello-world} {say} [--help] 421 | 422 | Commands: 423 | say Print something to the user. 424 | 425 | Options: 426 | --help -h Show this help message and exit. 427 | ]], 428 | }, _DATA) 429 | end) 430 | end) 431 | end) 432 | -------------------------------------------------------------------------------- /spec/plugin_template/telescope_spec.lua: -------------------------------------------------------------------------------- 1 | --- Make sure the Telescope integration works as expected. 2 | 3 | local mock_test = require("test_utilities.mock_test") 4 | local plugin_template = require("telescope._extensions.plugin_template") 5 | local runner = require("telescope._extensions.plugin_template.runner") 6 | local telescope_actions = require("telescope.actions") 7 | local telescope_actions_state = require("telescope.actions.state") 8 | 9 | local _ORIGINAL_GET_SELECTION_FUNCTION = runner.get_selection 10 | local _RESULT = nil 11 | 12 | --- Tempoarily wrap `runner.get_selection` so we can use it for unittests. 13 | local function _mock_get_selection() 14 | local function _mock(caller) 15 | ---@diagnostic disable-next-line: duplicate-set-field 16 | runner.get_selection = function(...) 17 | local selection = caller(...) 18 | _RESULT = selection 19 | 20 | return selection 21 | end 22 | end 23 | 24 | _mock(runner.get_selection) 25 | end 26 | 27 | --- Reset the unittests back to their original values. 28 | local function _restore_get_selection() 29 | runner.get_selection = _ORIGINAL_GET_SELECTION_FUNCTION 30 | 31 | _RESULT = nil 32 | end 33 | 34 | --- Run Neovim's event loop so that all currently-scheduled function can run. 35 | --- 36 | --- If you or some other API call `vim.schedule` / `vim.schedule_wrap` and want 37 | --- to make sure that function runs, call this function. 38 | --- 39 | ---@param timeout number? 40 | --- The milliseconds to wait before continuing. If the timeout is exceeded 41 | --- then we stop waiting for all of the functions to call. 42 | --- 43 | local function _wait_for_picker_to_initialize(timeout) 44 | if timeout == nil then 45 | timeout = 1000 46 | end 47 | 48 | local initialized = false 49 | 50 | vim.schedule(function() 51 | initialized = true 52 | end) 53 | 54 | vim.wait(timeout, function() 55 | return initialized 56 | end) 57 | end 58 | 59 | --- Wait for our (mocked) unittest variable to get some data back. 60 | --- 61 | ---@param timeout number? 62 | --- The milliseconds to wait before continuing. If the timeout is exceeded 63 | --- then we stop waiting for all of the functions to call. 64 | --- 65 | local function _wait_for_result(timeout) 66 | if timeout == nil then 67 | timeout = 1000 68 | end 69 | 70 | vim.wait(timeout, function() 71 | return _RESULT ~= nil 72 | end) 73 | end 74 | 75 | --- Create a Telescope picker for `command` and get the created "prompt" buffer back. 76 | --- 77 | ---@param command string 78 | --- A Telescope sub-command. e.g. If the command was `:Telescope 79 | --- plugin_template foo` then this function would require `"foo"`. 80 | --- 81 | local function _make_telescope_picker(command) 82 | plugin_template.exports[command]() 83 | 84 | _wait_for_picker_to_initialize() 85 | 86 | return vim.api.nvim_get_current_buf() 87 | end 88 | 89 | --- Setup all mocks and get ready for the unittest to run. 90 | local function _initialize_all() 91 | _mock_get_selection() 92 | mock_test.silence_all_internal_prints() 93 | end 94 | 95 | describe("telescope goodnight-moon", function() 96 | before_each(_initialize_all) 97 | after_each(_restore_get_selection) 98 | 99 | it("selects a book", function() 100 | local buffer = _make_telescope_picker("goodnight-moon") 101 | telescope_actions.select_default(buffer) 102 | _wait_for_result() 103 | 104 | assert.same({ "Buzz Bee - Cool Person" }, _RESULT) 105 | end) 106 | 107 | it("selects 2+ books", function() 108 | local buffer = _make_telescope_picker("goodnight-moon") 109 | local picker = telescope_actions_state.get_current_picker(buffer) 110 | picker:set_selection(picker.max_results - picker.manager:num_results()) 111 | telescope_actions.move_selection_previous(buffer) 112 | picker:toggle_selection(picker:get_selection_row()) 113 | telescope_actions.move_selection_previous(buffer) 114 | picker:toggle_selection(picker:get_selection_row()) 115 | telescope_actions.select_default(buffer) 116 | _wait_for_result() 117 | 118 | assert.same({ 119 | "Buzz Bee - Cool Person", 120 | "Fizz Drink - Some Name", 121 | }, _RESULT) 122 | end) 123 | end) 124 | 125 | describe("telescope hello-world", function() 126 | before_each(_initialize_all) 127 | after_each(_restore_get_selection) 128 | 129 | it("selects a phrase", function() 130 | local buffer = _make_telescope_picker("hello-world") 131 | telescope_actions.select_default(buffer) 132 | _wait_for_result() 133 | 134 | assert.same({ "What's up, doc?" }, _RESULT) 135 | end) 136 | 137 | it("selects 2+ phrases", function() 138 | local buffer = _make_telescope_picker("hello-world") 139 | local picker = telescope_actions_state.get_current_picker(buffer) 140 | picker:set_selection(picker.max_results - picker.manager:num_results()) 141 | telescope_actions.move_selection_previous(buffer) 142 | picker:toggle_selection(picker:get_selection_row()) 143 | telescope_actions.move_selection_previous(buffer) 144 | picker:toggle_selection(picker:get_selection_row()) 145 | telescope_actions.select_default(buffer) 146 | _wait_for_result() 147 | 148 | assert.same({ "What's up, doc?", "Hello, Sailor!" }, _RESULT) 149 | end) 150 | end) 151 | -------------------------------------------------------------------------------- /spec/test_utilities/mock_test.lua: -------------------------------------------------------------------------------- 1 | --- Temporarily track when certain built-in Vim commands are called. 2 | 3 | local M = {} 4 | 5 | local _ORIGINAL_INSPECT = vim.inspect 6 | local _DATA = nil 7 | 8 | --- Temporarily track vim.inspect calls. 9 | --- 10 | ---@param data any The passed value(s). 11 | --- 12 | local function _set_inspection_data(data) 13 | _DATA = data 14 | end 15 | 16 | ---@return any # Get all saved vim.inspect calls. 17 | function M.get_inspection_data() 18 | return _DATA 19 | end 20 | 21 | --- Temporarily track vim.inspect calls. 22 | function M.mock_vim_inspect() 23 | _ORIGINAL_INSPECT = vim.inspect 24 | vim.inspect = _set_inspection_data 25 | end 26 | 27 | --- Restore the previous vim.inspect function. 28 | function M.reset_mocked_vim_inspect() 29 | vim.inspect = _ORIGINAL_INSPECT 30 | end 31 | 32 | --- Make it so no existing API calls or commands print text. 33 | function M.silence_all_internal_prints() 34 | ---@diagnostic disable-next-line: duplicate-set-field 35 | vim.notify = function(...) end -- luacheck: ignore 212 36 | end 37 | 38 | return M 39 | -------------------------------------------------------------------------------- /spec/test_utilities/mock_vim.lua: -------------------------------------------------------------------------------- 1 | --- Temporarily track when certain built-in Vim commands are called. 2 | 3 | local M = {} 4 | 5 | local _ERROR_MESSAGES = {} 6 | local _ORIGINAL_HEALTH_ERROR = vim.health.error 7 | 8 | ---@return string[] # Get all saved vim.health.error calls. 9 | function M.get_vim_health_errors() 10 | return _ERROR_MESSAGES 11 | end 12 | 13 | --- Temporarily track vim.health calls. 14 | function M.mock_vim_health() 15 | local function _save_health_error_message(message) 16 | table.insert(_ERROR_MESSAGES, message) 17 | end 18 | 19 | vim.health.error = _save_health_error_message 20 | end 21 | 22 | --- Restore the previous vim.health function. 23 | function M.reset_mocked_vim_health() 24 | vim.health.error = _ORIGINAL_HEALTH_ERROR 25 | _ERROR_MESSAGES = {} 26 | end 27 | 28 | return M 29 | -------------------------------------------------------------------------------- /template.rockspec: -------------------------------------------------------------------------------- 1 | -- A template that will be replaced by the .github/workflows/release-luarocks.yml file 2 | -- 3 | -- Reference: 4 | -- Example template https://github.com/nvim-neorocks/luarocks-tag-release/blob/master/resources/rockspec.template 5 | -- 6 | 7 | local git_ref = "$git_ref" 8 | local modrev = "$modrev" 9 | local specrev = "$specrev" 10 | 11 | local repo_url = "$repo_url" 12 | 13 | rockspec_format = "3.0" 14 | package = "nvim-best-practices-plugin-template" 15 | version = modrev .. "-" .. specrev 16 | 17 | local user = "ColinKennedy" 18 | 19 | description = { 20 | homepage = "https://github.com/" .. user .. "/" .. package, 21 | labels = { "neovim", "neovim-plugin" }, 22 | license = "MIT", 23 | summary = 'A "Best Practices" Neovim plugin template', 24 | } 25 | 26 | dependencies = { 27 | "mega.cmdparse >= 1.0.3, < 2.0", 28 | "mega.logging >= 1.1.4, < 2.0", 29 | 30 | -- TODO(you): Remove these dependencies if you don't need them 31 | -- "lualine.nvim", -- Reference: https://luarocks.org/modules/neorocks/lualine.nvim 32 | "telescope.nvim >= 0.1.8 < 1.0", 33 | } 34 | 35 | test_dependencies = { 36 | "busted >= 2.0, < 3.0", 37 | "lua >= 5.1, < 6.0", 38 | } 39 | 40 | -- Reference: https://github.com/luarocks/luarocks/wiki/test#test-types 41 | test = { type = "busted" } 42 | 43 | source = { 44 | url = repo_url .. "/archive/" .. git_ref .. ".zip", 45 | dir = "$repo_name-" .. "$archive_dir_suffix", 46 | } 47 | 48 | if modrev == "scm" or modrev == "dev" then 49 | source = { 50 | url = repo_url:gsub("https", "git"), 51 | } 52 | end 53 | 54 | build = { 55 | type = "builtin", 56 | copy_directories = $copy_directories, 57 | } 58 | --------------------------------------------------------------------------------