From a2fe68b2487708e450aad8d99e3c72d29c1dbf25 Mon Sep 17 00:00:00 2001 From: Barrett Ruth Date: Fri, 20 Feb 2026 16:55:55 -0500 Subject: [PATCH] feat: initial release --- .busted | 9 ++ .editorconfig | 9 ++ .github/DISCUSSION_TEMPLATE/q-a.yaml | 17 ++ .github/ISSUE_TEMPLATE/bug_report.yaml | 85 ++++++++++ .github/ISSUE_TEMPLATE/config.yaml | 5 + .github/ISSUE_TEMPLATE/feature_request.yaml | 30 ++++ .github/workflows/luarocks.yaml | 21 +++ .github/workflows/quality.yaml | 89 +++++++++++ .github/workflows/test.yaml | 22 +++ .gitignore | 11 ++ .luarc.json | 8 + .pre-commit-config.yaml | 17 ++ .prettierignore | 1 + .prettierrc | 9 ++ LICENSE | 21 +++ README.md | 47 ++++++ blink-cmp-tmux-scm-1.rockspec | 30 ++++ flake.lock | 43 +++++ flake.nix | 35 +++++ lua/blink-cmp-tmux.lua | 155 ++++++++++++++++++ selene.toml | 1 + spec/helpers.lua | 54 +++++++ spec/minimal_init.lua | 34 ++++ spec/tmux_spec.lua | 166 ++++++++++++++++++++ stylua.toml | 8 + vim.toml | 33 ++++ 26 files changed, 960 insertions(+) create mode 100644 .busted create mode 100644 .editorconfig create mode 100644 .github/DISCUSSION_TEMPLATE/q-a.yaml create mode 100644 .github/ISSUE_TEMPLATE/bug_report.yaml create mode 100644 .github/ISSUE_TEMPLATE/config.yaml create mode 100644 .github/ISSUE_TEMPLATE/feature_request.yaml create mode 100644 .github/workflows/luarocks.yaml create mode 100644 .github/workflows/quality.yaml create mode 100644 .github/workflows/test.yaml create mode 100644 .gitignore create mode 100644 .luarc.json create mode 100644 .pre-commit-config.yaml create mode 100644 .prettierignore create mode 100644 .prettierrc create mode 100644 LICENSE create mode 100644 README.md create mode 100644 blink-cmp-tmux-scm-1.rockspec create mode 100644 flake.lock create mode 100644 flake.nix create mode 100644 lua/blink-cmp-tmux.lua create mode 100644 selene.toml create mode 100644 spec/helpers.lua create mode 100644 spec/minimal_init.lua create mode 100644 spec/tmux_spec.lua create mode 100644 stylua.toml create mode 100644 vim.toml diff --git a/.busted b/.busted new file mode 100644 index 0000000..53513b8 --- /dev/null +++ b/.busted @@ -0,0 +1,9 @@ +return { + _all = { + lua = 'nvim -l', + ROOT = { './spec/' }, + }, + default = { + verbose = true, + }, +} diff --git a/.editorconfig b/.editorconfig new file mode 100644 index 0000000..b9de190 --- /dev/null +++ b/.editorconfig @@ -0,0 +1,9 @@ +root = true + +[*] +insert_final_newline = true +charset = utf-8 + +[*.lua] +indent_style = space +indent_size = 2 diff --git a/.github/DISCUSSION_TEMPLATE/q-a.yaml b/.github/DISCUSSION_TEMPLATE/q-a.yaml new file mode 100644 index 0000000..397054c --- /dev/null +++ b/.github/DISCUSSION_TEMPLATE/q-a.yaml @@ -0,0 +1,17 @@ +title: 'Q&A' +labels: [] +body: + - type: markdown + attributes: + value: | + Use this space for questions, ideas, and general discussion about blink-cmp-tmux. + For bug reports, please [open an issue](https://github.com/barrettruth/blink-cmp-tmux/issues/new/choose) instead. + - type: textarea + attributes: + label: Question or topic + validations: + required: true + - type: textarea + attributes: + label: Context + description: Any relevant details (Neovim version, config, screenshots) diff --git a/.github/ISSUE_TEMPLATE/bug_report.yaml b/.github/ISSUE_TEMPLATE/bug_report.yaml new file mode 100644 index 0000000..686336b --- /dev/null +++ b/.github/ISSUE_TEMPLATE/bug_report.yaml @@ -0,0 +1,85 @@ +name: Bug Report +description: Report a bug +title: 'bug: ' +labels: [bug] +body: + - type: checkboxes + attributes: + label: Prerequisites + options: + - label: + I have searched [existing + issues](https://github.com/barrettruth/blink-cmp-tmux/issues) + required: true + - label: I have updated to the latest version + required: true + + - type: textarea + attributes: + label: 'Neovim version' + description: 'Output of `nvim --version`' + render: text + validations: + required: true + + - type: input + attributes: + label: 'Operating system' + placeholder: 'e.g. Arch Linux, macOS 15, Ubuntu 24.04' + validations: + required: true + + - type: textarea + attributes: + label: Description + description: What happened? What did you expect? + validations: + required: true + + - type: textarea + attributes: + label: Steps to reproduce + description: Minimal steps to trigger the bug + value: | + 1. + 2. + 3. + validations: + required: true + + - type: textarea + attributes: + label: Minimal reproduction + description: | + Save the script below as `repro.lua`, edit if needed, and run: + ``` + nvim -u repro.lua + ``` + Confirm the bug reproduces with this config before submitting. + render: lua + value: | + vim.env.LAZY_STDPATH = '.repro' + load(vim.fn.system('curl -s https://raw.githubusercontent.com/folke/lazy.nvim/main/bootstrap.lua'))() + require('lazy.nvim').setup({ + spec = { + { + 'saghen/blink.cmp', + dependencies = { + 'barrettruth/blink-cmp-tmux', + }, + opts = { + sources = { + default = { 'tmux' }, + providers = { + tmux = { + name = 'Tmux', + module = 'blink-cmp-tmux', + }, + }, + }, + }, + }, + }, + }) + validations: + required: true diff --git a/.github/ISSUE_TEMPLATE/config.yaml b/.github/ISSUE_TEMPLATE/config.yaml new file mode 100644 index 0000000..f8e805b --- /dev/null +++ b/.github/ISSUE_TEMPLATE/config.yaml @@ -0,0 +1,5 @@ +blank_issues_enabled: false +contact_links: + - name: Questions + url: https://github.com/barrettruth/blink-cmp-tmux/discussions + about: Ask questions and discuss ideas diff --git a/.github/ISSUE_TEMPLATE/feature_request.yaml b/.github/ISSUE_TEMPLATE/feature_request.yaml new file mode 100644 index 0000000..a8d4809 --- /dev/null +++ b/.github/ISSUE_TEMPLATE/feature_request.yaml @@ -0,0 +1,30 @@ +name: Feature Request +description: Suggest a feature +title: 'feat: ' +labels: [enhancement] +body: + - type: checkboxes + attributes: + label: Prerequisites + options: + - label: + I have searched [existing + issues](https://github.com/barrettruth/blink-cmp-tmux/issues) + required: true + + - type: textarea + attributes: + label: Problem + description: What problem does this solve? + validations: + required: true + + - type: textarea + attributes: + label: Proposed solution + validations: + required: true + + - type: textarea + attributes: + label: Alternatives considered diff --git a/.github/workflows/luarocks.yaml b/.github/workflows/luarocks.yaml new file mode 100644 index 0000000..9b6664e --- /dev/null +++ b/.github/workflows/luarocks.yaml @@ -0,0 +1,21 @@ +name: luarocks + +on: + push: + tags: + - 'v*' + +jobs: + quality: + uses: ./.github/workflows/quality.yaml + + publish: + needs: quality + runs-on: ubuntu-latest + + steps: + - uses: actions/checkout@v4 + + - uses: nvim-neorocks/luarocks-tag-release@v7 + env: + LUAROCKS_API_KEY: ${{ secrets.LUAROCKS_API_KEY }} diff --git a/.github/workflows/quality.yaml b/.github/workflows/quality.yaml new file mode 100644 index 0000000..77049fd --- /dev/null +++ b/.github/workflows/quality.yaml @@ -0,0 +1,89 @@ +name: quality + +on: + workflow_call: + pull_request: + branches: [main] + push: + branches: [main] + +jobs: + changes: + runs-on: ubuntu-latest + outputs: + lua: ${{ steps.changes.outputs.lua }} + markdown: ${{ steps.changes.outputs.markdown }} + steps: + - uses: actions/checkout@v4 + - uses: dorny/paths-filter@v3 + id: changes + with: + filters: | + lua: + - 'lua/**' + - 'plugin/**' + - '*.lua' + - '.luarc.json' + - '*.toml' + markdown: + - '*.md' + + lua-format: + name: Lua Format Check + runs-on: ubuntu-latest + needs: changes + if: ${{ needs.changes.outputs.lua == 'true' }} + steps: + - uses: actions/checkout@v4 + - uses: JohnnyMorganz/stylua-action@v4 + with: + token: ${{ secrets.GITHUB_TOKEN }} + version: 2.1.0 + args: --check . + + lua-lint: + name: Lua Lint Check + runs-on: ubuntu-latest + needs: changes + if: ${{ needs.changes.outputs.lua == 'true' }} + steps: + - uses: actions/checkout@v4 + - name: Lint with Selene + uses: NTBBloodbath/selene-action@v1.0.0 + with: + token: ${{ secrets.GITHUB_TOKEN }} + args: --display-style quiet . + + lua-typecheck: + name: Lua Type Check + runs-on: ubuntu-latest + needs: changes + if: ${{ needs.changes.outputs.lua == 'true' }} + steps: + - uses: actions/checkout@v4 + - name: Run Lua LS Type Check + uses: mrcjkb/lua-typecheck-action@v0 + with: + checklevel: Warning + directories: lua + configpath: .luarc.json + + markdown-format: + name: Markdown Format Check + runs-on: ubuntu-latest + needs: changes + if: ${{ needs.changes.outputs.markdown == 'true' }} + steps: + - uses: actions/checkout@v4 + - name: Setup pnpm + uses: pnpm/action-setup@v4 + with: + version: 8 + - name: Setup Node.js + uses: actions/setup-node@v4 + with: + node-version: '20' + - name: Install prettier + run: pnpm add -g prettier@3.1.0 + - name: Check markdown formatting with prettier + run: prettier --check . diff --git a/.github/workflows/test.yaml b/.github/workflows/test.yaml new file mode 100644 index 0000000..6910389 --- /dev/null +++ b/.github/workflows/test.yaml @@ -0,0 +1,22 @@ +name: test + +on: + pull_request: + branches: [main] + push: + branches: [main] + +jobs: + test: + runs-on: ubuntu-latest + strategy: + fail-fast: false + matrix: + nvim: [stable, nightly] + name: Test (Neovim ${{ matrix.nvim }}) + steps: + - uses: actions/checkout@v4 + + - uses: nvim-neorocks/nvim-busted-action@v1 + with: + nvim_version: ${{ matrix.nvim }} diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..e8b868c --- /dev/null +++ b/.gitignore @@ -0,0 +1,11 @@ +*.log + +.*cache* +CLAUDE.md +.claude/ + +node_modules/ + +result +result-* +.direnv/ diff --git a/.luarc.json b/.luarc.json new file mode 100644 index 0000000..b438cce --- /dev/null +++ b/.luarc.json @@ -0,0 +1,8 @@ +{ + "runtime.version": "Lua 5.1", + "runtime.path": ["lua/?.lua", "lua/?/init.lua"], + "diagnostics.globals": ["vim", "jit"], + "workspace.library": ["$VIMRUNTIME/lua", "${3rd}/luv/library"], + "workspace.checkThirdParty": false, + "completion.callSnippet": "Replace" +} diff --git a/.pre-commit-config.yaml b/.pre-commit-config.yaml new file mode 100644 index 0000000..5d1f13f --- /dev/null +++ b/.pre-commit-config.yaml @@ -0,0 +1,17 @@ +minimum_pre_commit_version: '3.5.0' + +repos: + - repo: https://github.com/JohnnyMorganz/StyLua + rev: v2.3.1 + hooks: + - id: stylua-github + name: stylua (Lua formatter) + files: \.lua$ + pass_filenames: true + + - repo: https://github.com/pre-commit/mirrors-prettier + rev: v4.0.0-alpha.8 + hooks: + - id: prettier + name: prettier + files: \.(md|toml|yaml|yml|sh)$ diff --git a/.prettierignore b/.prettierignore new file mode 100644 index 0000000..c2658d7 --- /dev/null +++ b/.prettierignore @@ -0,0 +1 @@ +node_modules/ diff --git a/.prettierrc b/.prettierrc new file mode 100644 index 0000000..0663621 --- /dev/null +++ b/.prettierrc @@ -0,0 +1,9 @@ +{ + "proseWrap": "always", + "printWidth": 80, + "tabWidth": 2, + "useTabs": false, + "trailingComma": "none", + "semi": false, + "singleQuote": true +} diff --git a/LICENSE b/LICENSE new file mode 100644 index 0000000..fee363a --- /dev/null +++ b/LICENSE @@ -0,0 +1,21 @@ +MIT License + +Copyright (c) 2025 Raphael + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all +copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +SOFTWARE. diff --git a/README.md b/README.md new file mode 100644 index 0000000..f11c1b1 --- /dev/null +++ b/README.md @@ -0,0 +1,47 @@ +# blink-cmp-tmux + +Tmux command completion source for +[blink.cmp](https://github.com/saghen/blink.cmp). + +## Features + +- Completes tmux commands with full usage signatures +- Includes alias information for commands +- Shows man page descriptions in documentation + +## Requirements + +- Neovim 0.10.0+ +- [blink.cmp](https://github.com/saghen/blink.cmp) +- tmux + +## Installation + +Install via +[luarocks](https://luarocks.org/modules/barrettruth/blink-cmp-tmux): + +``` +luarocks install blink-cmp-tmux +``` + +Or with lazy.nvim: + +```lua +{ + 'saghen/blink.cmp', + dependencies = { + 'barrettruth/blink-cmp-tmux', + }, + opts = { + sources = { + default = { 'tmux' }, + providers = { + tmux = { + name = 'Tmux', + module = 'blink-cmp-tmux', + }, + }, + }, + }, +} +``` diff --git a/blink-cmp-tmux-scm-1.rockspec b/blink-cmp-tmux-scm-1.rockspec new file mode 100644 index 0000000..6de0568 --- /dev/null +++ b/blink-cmp-tmux-scm-1.rockspec @@ -0,0 +1,30 @@ +rockspec_format = '3.0' +package = 'blink-cmp-tmux' +version = 'scm-1' + +source = { + url = 'git+https://github.com/barrettruth/blink-cmp-tmux.git', +} + +description = { + summary = 'Tmux command completion source for blink.cmp', + homepage = 'https://github.com/barrettruth/blink-cmp-tmux', + license = 'MIT', +} + +dependencies = { + 'lua >= 5.1', +} + +test_dependencies = { + 'nlua', + 'busted >= 2.1.1', +} + +test = { + type = 'busted', +} + +build = { + type = 'builtin', +} diff --git a/flake.lock b/flake.lock new file mode 100644 index 0000000..7501bfa --- /dev/null +++ b/flake.lock @@ -0,0 +1,43 @@ +{ + "nodes": { + "nixpkgs": { + "locked": { + "lastModified": 1771207753, + "narHash": "sha256-b9uG8yN50DRQ6A7JdZBfzq718ryYrlmGgqkRm9OOwCE=", + "owner": "NixOS", + "repo": "nixpkgs", + "rev": "d1c15b7d5806069da59e819999d70e1cec0760bf", + "type": "github" + }, + "original": { + "owner": "NixOS", + "ref": "nixpkgs-unstable", + "repo": "nixpkgs", + "type": "github" + } + }, + "root": { + "inputs": { + "nixpkgs": "nixpkgs", + "systems": "systems" + } + }, + "systems": { + "locked": { + "lastModified": 1681028828, + "narHash": "sha256-Vy1rq5AaRuLzOxct8nz4T6wlgyUR7zLU309k9mBC768=", + "owner": "nix-systems", + "repo": "default", + "rev": "da67096a3b9bf56a91d16901293e51ba5b49a27e", + "type": "github" + }, + "original": { + "owner": "nix-systems", + "repo": "default", + "type": "github" + } + } + }, + "root": "root", + "version": 7 +} diff --git a/flake.nix b/flake.nix new file mode 100644 index 0000000..7d8feed --- /dev/null +++ b/flake.nix @@ -0,0 +1,35 @@ +{ + description = "blink-cmp-tmux — tmux command completion source for blink.cmp"; + + inputs = { + nixpkgs.url = "github:NixOS/nixpkgs/nixpkgs-unstable"; + systems.url = "github:nix-systems/default"; + }; + + outputs = + { + nixpkgs, + systems, + ... + }: + let + forEachSystem = f: nixpkgs.lib.genAttrs (import systems) (system: f nixpkgs.legacyPackages.${system}); + in + { + devShells = forEachSystem (pkgs: { + default = pkgs.mkShell { + packages = [ + (pkgs.luajit.withPackages ( + ps: with ps; [ + busted + nlua + ] + )) + pkgs.prettier + pkgs.stylua + pkgs.selene + ]; + }; + }); + }; +} diff --git a/lua/blink-cmp-tmux.lua b/lua/blink-cmp-tmux.lua new file mode 100644 index 0000000..5faf8f7 --- /dev/null +++ b/lua/blink-cmp-tmux.lua @@ -0,0 +1,155 @@ +---@class blink-cmp-tmux : blink.cmp.Source +local M = {} + +---@type blink.cmp.CompletionItem[]? +local cache = nil + +function M.new() + return setmetatable({}, { __index = M }) +end + +---@return boolean +function M.enabled() + return vim.bo.filetype == 'tmux' +end + +---@return table +local function parse_descriptions() + local result = vim.system({ 'bash', '-c', 'MANWIDTH=80 man -P cat tmux 2>/dev/null' }):wait() + local stdout = result.stdout or '' + local lines = {} + for line in (stdout .. '\n'):gmatch('(.-)\n') do + lines[#lines + 1] = line + end + + local cmd_result = vim.system({ 'tmux', 'list-commands', '-F', '#{command_list_name}' }):wait() + local cmds = {} + for name in (cmd_result.stdout or ''):gmatch('[^\n]+') do + cmds[name] = true + end + + local defs = {} + for i, line in ipairs(lines) do + local cmd = line:match('^ ([a-z][a-z-]+)') + if cmd and cmds[cmd] then + local rest = line:sub(8 + #cmd) + if rest == '' or rest:match('^%s+%[') or rest:match('^%s%s+') then + defs[#defs + 1] = { line = i, cmd = cmd } + end + end + end + + local descs = {} + for idx, def in ipairs(defs) do + local block_end = (defs[idx + 1] and defs[idx + 1].line or #lines) - 1 + local j = def.line + 1 + while j <= block_end do + local l = lines[j] + if l:match('^%s+%(alias:') then + j = j + 1 + elseif l:match('^ ') then + local stripped = vim.trim(l) + if stripped == '' or stripped:match('[%[%]]') then + j = j + 1 + else + break + end + elseif vim.trim(l) == '' then + j = j + 1 + else + break + end + end + + local desc_lines = {} + for k = j, block_end do + desc_lines[#desc_lines + 1] = lines[k] + end + local paragraphs = { {} } + for _, dl in ipairs(desc_lines) do + local stripped = vim.trim(dl) + if stripped == '' then + if #paragraphs[#paragraphs] > 0 then + paragraphs[#paragraphs + 1] = {} + end + else + local para = paragraphs[#paragraphs] + para[#para + 1] = stripped + end + end + local parts = {} + for _, para in ipairs(paragraphs) do + if #para > 0 then + parts[#parts + 1] = table.concat(para, ' ') + end + end + local desc = table.concat(parts, '\n\n') + desc = desc:gsub('\xe2\x80\x90 ', '') + desc = desc:gsub(' +', ' ') + if desc ~= '' then + descs[def.cmd] = desc + end + end + return descs +end + +---@param output string +---@param descs table +---@return blink.cmp.CompletionItem[] +local function parse(output, descs) + local Kind = require('blink.cmp.types').CompletionItemKind + local items = {} + for line in output:gmatch('[^\n]+') do + local name, alias = line:match('^([a-z-]+)%s+%(([a-z-]+)%)') + if not name then + name = line:match('^([a-z-]+)') + end + if name then + local doc_parts = {} + if alias then + doc_parts[#doc_parts + 1] = ('**alias**: `%s`\n'):format(alias) + end + doc_parts[#doc_parts + 1] = '```\n' .. line .. '\n```' + if descs[name] then + doc_parts[#doc_parts + 1] = '\n---\n\n' .. descs[name] + end + items[#items + 1] = { + label = name, + kind = Kind.Keyword, + documentation = { + kind = 'markdown', + value = table.concat(doc_parts), + }, + } + end + end + return items +end + +---@param ctx blink.cmp.Context +---@param callback fun(response: blink.cmp.CompletionResponse) +---@return fun() +function M:get_completions(ctx, callback) + if not cache then + local ok, descs = pcall(parse_descriptions) + if not ok then + descs = {} + end + local result = vim.system({ 'tmux', 'list-commands' }):wait() + cache = parse(result.stdout or '', descs) + end + + local before = ctx.line:sub(1, ctx.cursor[2]) + if before:match('^%s*[a-z-]*$') then + callback({ + is_incomplete_forward = false, + is_incomplete_backward = false, + items = vim.deepcopy(cache), + }) + else + callback({ items = {} }) + end + return function() end +end + +return M diff --git a/selene.toml b/selene.toml new file mode 100644 index 0000000..96cf5ab --- /dev/null +++ b/selene.toml @@ -0,0 +1 @@ +std = 'vim' diff --git a/spec/helpers.lua b/spec/helpers.lua new file mode 100644 index 0000000..1635e3c --- /dev/null +++ b/spec/helpers.lua @@ -0,0 +1,54 @@ +local plugin_dir = vim.fn.getcwd() +vim.opt.runtimepath:prepend(plugin_dir) + +if not package.loaded['blink.cmp.types'] then + package.loaded['blink.cmp.types'] = { + CompletionItemKind = { + Text = 1, + Method = 2, + Function = 3, + Constructor = 4, + Field = 5, + Variable = 6, + Class = 7, + Interface = 8, + Module = 9, + Property = 10, + Unit = 11, + Value = 12, + Enum = 13, + Keyword = 14, + Snippet = 15, + Color = 16, + File = 17, + Reference = 18, + Folder = 19, + EnumMember = 20, + Constant = 21, + Struct = 22, + Event = 23, + Operator = 24, + TypeParameter = 25, + }, + } +end + +local M = {} + +function M.create_buffer(lines, filetype) + local bufnr = vim.api.nvim_create_buf(false, true) + vim.api.nvim_buf_set_lines(bufnr, 0, -1, false, lines or {}) + if filetype then + vim.api.nvim_set_option_value('filetype', filetype, { buf = bufnr }) + end + vim.api.nvim_set_current_buf(bufnr) + return bufnr +end + +function M.delete_buffer(bufnr) + if bufnr and vim.api.nvim_buf_is_valid(bufnr) then + vim.api.nvim_buf_delete(bufnr, { force = true }) + end +end + +return M diff --git a/spec/minimal_init.lua b/spec/minimal_init.lua new file mode 100644 index 0000000..6fea9dd --- /dev/null +++ b/spec/minimal_init.lua @@ -0,0 +1,34 @@ +vim.cmd([[set runtimepath=$VIMRUNTIME]]) +vim.opt.runtimepath:append('.') +vim.opt.packpath = {} +vim.opt.loadplugins = false + +package.loaded['blink.cmp.types'] = { + CompletionItemKind = { + Text = 1, + Method = 2, + Function = 3, + Constructor = 4, + Field = 5, + Variable = 6, + Class = 7, + Interface = 8, + Module = 9, + Property = 10, + Unit = 11, + Value = 12, + Enum = 13, + Keyword = 14, + Snippet = 15, + Color = 16, + File = 17, + Reference = 18, + Folder = 19, + EnumMember = 20, + Constant = 21, + Struct = 22, + Event = 23, + Operator = 24, + TypeParameter = 25, + }, +} diff --git a/spec/tmux_spec.lua b/spec/tmux_spec.lua new file mode 100644 index 0000000..3f47a97 --- /dev/null +++ b/spec/tmux_spec.lua @@ -0,0 +1,166 @@ +local helpers = require('spec.helpers') + +local TMUX_COMMANDS = table.concat({ + 'bind-key (bind) [-lnrN:T:] key command [arguments]', + 'display-message (display) [-aINpv] [-c target-client] [-d delay] [-t target-pane] [message]', + 'set-option (set) [-aFgopqsuUw] [-t target-pane] option [value]', +}, '\n') + +local TMUX_NAMES = 'bind-key\ndisplay-message\nset-option\n' + +local MAN_PAGE = table.concat({ + ' bind-key [-lnrN:T:] key command [arguments]', + ' (alias: bind)', + '', + ' Bind a key to a command.', + '', + ' display-message [-aINpv] [-c target-client] [-d delay] [-t target-pane] [message]', + ' (alias: display)', + '', + ' Display a message.', + '', + ' set-option [-aFgopqsuUw] [-t target-pane] option [value]', + ' (alias: set)', + '', + ' Set a window option.', +}, '\n') + +local function mock_system() + local original = vim.system + ---@diagnostic disable-next-line: duplicate-set-field + vim.system = function(cmd) + if cmd[1] == 'bash' then + return { + wait = function() + return { stdout = MAN_PAGE, code = 0 } + end, + } + elseif cmd[1] == 'tmux' and cmd[2] == 'list-commands' then + if cmd[3] == '-F' then + return { + wait = function() + return { stdout = TMUX_NAMES, code = 0 } + end, + } + end + return { + wait = function() + return { stdout = TMUX_COMMANDS, code = 0 } + end, + } + end + return { + wait = function() + return { stdout = '', code = 1 } + end, + } + end + return function() + vim.system = original + end +end + +describe('blink-cmp-tmux', function() + local restore + + before_each(function() + package.loaded['blink-cmp-tmux'] = nil + restore = mock_system() + end) + + after_each(function() + if restore then + restore() + end + end) + + describe('enabled', function() + it('returns true for tmux filetype', function() + local bufnr = helpers.create_buffer({}, 'tmux') + local source = require('blink-cmp-tmux') + assert.is_true(source.enabled()) + helpers.delete_buffer(bufnr) + end) + + it('returns false for other filetypes', function() + local bufnr = helpers.create_buffer({}, 'lua') + local source = require('blink-cmp-tmux') + assert.is_false(source.enabled()) + helpers.delete_buffer(bufnr) + end) + end) + + describe('get_completions', function() + it('returns items with Keyword kind', function() + local source = require('blink-cmp-tmux').new() + local items + source:get_completions({ line = '', cursor = { 1, 0 } }, function(response) + items = response.items + end) + assert.is_not_nil(items) + assert.equals(3, #items) + for _, item in ipairs(items) do + assert.equals(14, item.kind) + end + end) + + it('returns items on empty line', function() + local source = require('blink-cmp-tmux').new() + local items + source:get_completions({ line = '', cursor = { 1, 0 } }, function(response) + items = response.items + end) + assert.equals(3, #items) + end) + + it('returns items when typing command prefix', function() + local source = require('blink-cmp-tmux').new() + local items + source:get_completions({ line = 'bind', cursor = { 1, 4 } }, function(response) + items = response.items + end) + assert.is_true(#items > 0) + end) + + it('returns empty after command arguments', function() + local source = require('blink-cmp-tmux').new() + local items + source:get_completions({ line = 'bind-key -n ', cursor = { 1, 12 } }, function(response) + items = response.items + end) + assert.equals(0, #items) + end) + + it('includes documentation with man page description', function() + local source = require('blink-cmp-tmux').new() + local items + source:get_completions({ line = '', cursor = { 1, 0 } }, function(response) + items = response.items + end) + local bind = vim.iter(items):find(function(item) + return item.label == 'bind-key' + end) + assert.is_not_nil(bind) + assert.is_not_nil(bind.documentation) + assert.is_truthy(bind.documentation.value:find('Bind a key')) + end) + + it('includes alias in documentation', function() + local source = require('blink-cmp-tmux').new() + local items + source:get_completions({ line = '', cursor = { 1, 0 } }, function(response) + items = response.items + end) + local bind = vim.iter(items):find(function(item) + return item.label == 'bind-key' + end) + assert.is_truthy(bind.documentation.value:find('alias')) + end) + + it('returns a cancel function', function() + local source = require('blink-cmp-tmux').new() + local cancel = source:get_completions({ line = '', cursor = { 1, 0 } }, function() end) + assert.is_function(cancel) + end) + end) +end) diff --git a/stylua.toml b/stylua.toml new file mode 100644 index 0000000..01ded03 --- /dev/null +++ b/stylua.toml @@ -0,0 +1,8 @@ +column_width = 100 +line_endings = "Unix" +indent_type = "Spaces" +indent_width = 2 +quote_style = "AutoPreferSingle" +call_parentheses = "Always" +[sort_requires] +enabled = true diff --git a/vim.toml b/vim.toml new file mode 100644 index 0000000..3a84ac2 --- /dev/null +++ b/vim.toml @@ -0,0 +1,33 @@ +[selene] +base = "lua51" +name = "vim" + +[vim] +any = true + +[jit] +any = true + +[bit] +any = true + +[assert] +any = true + +[describe] +any = true + +[it] +any = true + +[before_each] +any = true + +[after_each] +any = true + +[spy] +any = true + +[stub] +any = true