From 9fdd3b3133daa7df986e570d1951b743728d96a1 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-ghostty-scm-1.rockspec | 30 ++++ flake.lock | 43 +++++ flake.nix | 35 ++++ lua/blink-cmp-ghostty.lua | 124 ++++++++++++++ selene.toml | 1 + spec/ghostty_spec.lua | 175 ++++++++++++++++++++ spec/helpers.lua | 54 ++++++ spec/minimal_init.lua | 34 ++++ stylua.toml | 8 + vim.toml | 33 ++++ 26 files changed, 938 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-ghostty-scm-1.rockspec create mode 100644 flake.lock create mode 100644 flake.nix create mode 100644 lua/blink-cmp-ghostty.lua create mode 100644 selene.toml create mode 100644 spec/ghostty_spec.lua create mode 100644 spec/helpers.lua create mode 100644 spec/minimal_init.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..8d1b9a4 --- /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-ghostty. + For bug reports, please [open an issue](https://github.com/barrettruth/blink-cmp-ghostty/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..3c2af4f --- /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-ghostty/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-ghostty', + }, + opts = { + sources = { + default = { 'ghostty' }, + providers = { + ghostty = { + name = 'Ghostty', + module = 'blink-cmp-ghostty', + }, + }, + }, + }, + }, + }, + }) + validations: + required: true diff --git a/.github/ISSUE_TEMPLATE/config.yaml b/.github/ISSUE_TEMPLATE/config.yaml new file mode 100644 index 0000000..b60d1f2 --- /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-ghostty/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..0189da9 --- /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-ghostty/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..5bd4a4b --- /dev/null +++ b/README.md @@ -0,0 +1,47 @@ +# blink-cmp-ghostty + +Ghostty configuration completion source for +[blink.cmp](https://github.com/saghen/blink.cmp). + +## Features + +- Completes Ghostty configuration keys with documentation +- Provides enum values for configuration options +- Documentation extracted from `ghostty +show-config --docs` + +## Requirements + +- Neovim 0.10.0+ +- [blink.cmp](https://github.com/saghen/blink.cmp) +- [Ghostty](https://ghostty.org) + +## Installation + +Install via +[luarocks](https://luarocks.org/modules/barrettruth/blink-cmp-ghostty): + +``` +luarocks install blink-cmp-ghostty +``` + +Or with lazy.nvim: + +```lua +{ + 'saghen/blink.cmp', + dependencies = { + 'barrettruth/blink-cmp-ghostty', + }, + opts = { + sources = { + default = { 'ghostty' }, + providers = { + ghostty = { + name = 'Ghostty', + module = 'blink-cmp-ghostty', + }, + }, + }, + }, +} +``` diff --git a/blink-cmp-ghostty-scm-1.rockspec b/blink-cmp-ghostty-scm-1.rockspec new file mode 100644 index 0000000..cd8c1cf --- /dev/null +++ b/blink-cmp-ghostty-scm-1.rockspec @@ -0,0 +1,30 @@ +rockspec_format = '3.0' +package = 'blink-cmp-ghostty' +version = 'scm-1' + +source = { + url = 'git+https://github.com/barrettruth/blink-cmp-ghostty.git', +} + +description = { + summary = 'Ghostty configuration completion source for blink.cmp', + homepage = 'https://github.com/barrettruth/blink-cmp-ghostty', + 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..d49a582 --- /dev/null +++ b/flake.nix @@ -0,0 +1,35 @@ +{ + description = "blink-cmp-ghostty — Ghostty configuration 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-ghostty.lua b/lua/blink-cmp-ghostty.lua new file mode 100644 index 0000000..2efd951 --- /dev/null +++ b/lua/blink-cmp-ghostty.lua @@ -0,0 +1,124 @@ +---@class blink-cmp-ghostty : blink.cmp.Source +local M = {} + +---@type blink.cmp.CompletionItem[]? +local keys_cache = nil +---@type table? +local enums_cache = nil + +function M.new() + return setmetatable({}, { __index = M }) +end + +---@return boolean +function M.enabled() + return vim.bo.filetype == 'ghostty' +end + +---@return blink.cmp.CompletionItem[] +local function parse_keys() + local Kind = require('blink.cmp.types').CompletionItemKind + local result = vim.system({ 'ghostty', '+show-config', '--docs' }):wait() + local items = {} + local doc_lines = {} + + for line in ((result.stdout or '') .. '\n'):gmatch('(.-)\n') do + if line:match('^#') then + local stripped = line:gsub('^# ?', '') + doc_lines[#doc_lines + 1] = stripped + else + local key = line:match('^([a-z][a-z0-9-]*)%s*=') + if key then + local doc = #doc_lines > 0 and table.concat(doc_lines, '\n') or nil + items[#items + 1] = { + label = key, + kind = Kind.Property, + documentation = doc and { kind = 'markdown', value = doc } or nil, + } + end + doc_lines = {} + end + end + return items +end + +---@return table +local function parse_enums() + local bin = vim.fn.exepath('ghostty') + if bin == '' then + return {} + end + local real = vim.uv.fs_realpath(bin) + if not real then + return {} + end + local prefix = real:match('(.*)/bin/ghostty$') + if not prefix then + return {} + end + local path = prefix .. '/share/bash-completion/completions/ghostty.bash' + local fd = io.open(path, 'r') + if not fd then + return {} + end + local content = fd:read('*a') + fd:close() + + local enums = {} + for key, values in content:gmatch('%-%-([a-z][a-z0-9-]*)%) [^\n]* compgen %-W "([^"]+)"') do + local vals = {} + for v in values:gmatch('%S+') do + vals[#vals + 1] = v + end + if #vals > 0 then + enums[key] = vals + end + end + return enums +end + +---@param ctx blink.cmp.Context +---@param callback fun(response: blink.cmp.CompletionResponse) +---@return fun() +function M:get_completions(ctx, callback) + if not keys_cache then + keys_cache = parse_keys() + enums_cache = parse_enums() + end + + local line = ctx.line + local col = ctx.cursor[2] + local eq_pos = line:find('=') + + if eq_pos and col > eq_pos then + local key = vim.trim(line:sub(1, eq_pos - 1)) + local vals = enums_cache[key] + if vals then + local Kind = require('blink.cmp.types').CompletionItemKind + local items = {} + for _, v in ipairs(vals) do + items[#items + 1] = { + label = v, + kind = Kind.EnumMember, + filterText = v, + } + end + callback({ + is_incomplete_forward = false, + is_incomplete_backward = false, + items = items, + }) + return function() end + end + callback({ items = {} }) + else + callback({ + is_incomplete_forward = false, + is_incomplete_backward = false, + items = vim.deepcopy(keys_cache), + }) + 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/ghostty_spec.lua b/spec/ghostty_spec.lua new file mode 100644 index 0000000..5c7e804 --- /dev/null +++ b/spec/ghostty_spec.lua @@ -0,0 +1,175 @@ +local helpers = require('spec.helpers') + +local CONFIG_DOCS = table.concat({ + '# The font family to use.', + '# This can be a comma-separated list of font families.', + 'font-family = default', + '', + '# The font size in points.', + 'font-size = 12', + '', + '# The cursor style.', + 'cursor-style = block', + '', +}, '\n') + +local BASH_COMPLETION = table.concat({ + ' --cursor-style) mapfile -t COMPREPLY < <( compgen -W "block bar underline" -- "$cur" ); _add_spaces ;;', + ' --font-style) mapfile -t COMPREPLY < <( compgen -W "normal italic" -- "$cur" ); _add_spaces ;;', +}, '\n') + +local function mock_system() + local original = vim.system + ---@diagnostic disable-next-line: duplicate-set-field + vim.system = function(cmd) + if cmd[1] == 'ghostty' then + return { + wait = function() + return { stdout = CONFIG_DOCS, code = 0 } + end, + } + end + return { + wait = function() + return { stdout = '', code = 1 } + end, + } + end + return function() + vim.system = original + end +end + +local function mock_enums() + local original_exepath = vim.fn.exepath + local original_realpath = vim.uv.fs_realpath + local original_open = io.open + + vim.fn.exepath = function(name) + if name == 'ghostty' then + return '/mock/bin/ghostty' + end + return original_exepath(name) + end + vim.uv.fs_realpath = function(path) + if path == '/mock/bin/ghostty' then + return '/mock/bin/ghostty' + end + return original_realpath(path) + end + io.open = function(path, mode) + if path:match('ghostty%.bash$') then + return { + read = function() + return BASH_COMPLETION + end, + close = function() end, + } + end + return original_open(path, mode) + end + + return function() + vim.fn.exepath = original_exepath + vim.uv.fs_realpath = original_realpath + io.open = original_open + end +end + +describe('blink-cmp-ghostty', function() + local restores = {} + + before_each(function() + package.loaded['blink-cmp-ghostty'] = nil + end) + + after_each(function() + for _, fn in ipairs(restores) do + fn() + end + restores = {} + end) + + describe('enabled', function() + it('returns true for ghostty filetype', function() + local bufnr = helpers.create_buffer({}, 'ghostty') + local source = require('blink-cmp-ghostty') + 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-ghostty') + assert.is_false(source.enabled()) + helpers.delete_buffer(bufnr) + end) + end) + + describe('get_completions', function() + it('returns config keys before =', function() + restores[#restores + 1] = mock_system() + restores[#restores + 1] = mock_enums() + local source = require('blink-cmp-ghostty').new() + local items + source:get_completions({ line = 'font', cursor = { 1, 4 } }, function(response) + items = response.items + end) + assert.is_not_nil(items) + assert.equals(3, #items) + for _, item in ipairs(items) do + assert.equals(10, item.kind) + end + end) + + it('includes documentation from config docs', function() + restores[#restores + 1] = mock_system() + restores[#restores + 1] = mock_enums() + local source = require('blink-cmp-ghostty').new() + local items + source:get_completions({ line = '', cursor = { 1, 0 } }, function(response) + items = response.items + end) + local font_family = vim.iter(items):find(function(item) + return item.label == 'font-family' + end) + assert.is_not_nil(font_family) + assert.is_not_nil(font_family.documentation) + assert.is_truthy(font_family.documentation.value:find('font family')) + end) + + it('returns enum values after =', function() + restores[#restores + 1] = mock_system() + restores[#restores + 1] = mock_enums() + local source = require('blink-cmp-ghostty').new() + local items + source:get_completions({ line = 'cursor-style = ', cursor = { 1, 15 } }, function(response) + items = response.items + end) + assert.is_not_nil(items) + assert.equals(3, #items) + for _, item in ipairs(items) do + assert.equals(20, item.kind) + end + end) + + it('returns empty after = for unknown key', function() + restores[#restores + 1] = mock_system() + restores[#restores + 1] = mock_enums() + local source = require('blink-cmp-ghostty').new() + local items + source:get_completions({ line = 'font-family = ', cursor = { 1, 14 } }, function(response) + items = response.items + end) + assert.equals(0, #items) + end) + + it('returns a cancel function', function() + restores[#restores + 1] = mock_system() + restores[#restores + 1] = mock_enums() + local source = require('blink-cmp-ghostty').new() + local cancel = source:get_completions({ line = '', cursor = { 1, 0 } }, function() end) + assert.is_function(cancel) + end) + end) +end) 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/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