From 0b15a012500701b5cf996cd519a04adb5ddfe6aa Mon Sep 17 00:00:00 2001 From: Stephen Waits Date: Thu, 9 Apr 2026 14:01:03 -0600 Subject: [PATCH] feat: initial release --- .github/FUNDING.yml | 1 + .github/ISSUE_TEMPLATE/bug_report.md | 21 +++++++ .github/ISSUE_TEMPLATE/feature_request.md | 16 ++++++ .github/workflows/ci.yml | 30 ++++++++++ .gitignore | 6 ++ CHANGELOG.md | 18 ++++++ LICENSE | 21 +++++++ README.md | 52 +++++++++++++++++ doc/tiny-lightbulb.txt | 68 +++++++++++++++++++++++ lua/tiny-lightbulb/init.lua | 55 ++++++++++++++++++ plugin/tiny-lightbulb.lua | 1 + scripts/minimal_init.lua | 4 ++ scripts/test.sh | 11 ++++ selene.toml | 1 + stylua.toml | 4 ++ tests/test_init.lua | 36 ++++++++++++ vim.toml | 2 + 17 files changed, 347 insertions(+) create mode 100644 .github/FUNDING.yml create mode 100644 .github/ISSUE_TEMPLATE/bug_report.md create mode 100644 .github/ISSUE_TEMPLATE/feature_request.md create mode 100644 .github/workflows/ci.yml create mode 100644 .gitignore create mode 100644 CHANGELOG.md create mode 100644 LICENSE create mode 100644 README.md create mode 100644 doc/tiny-lightbulb.txt create mode 100644 lua/tiny-lightbulb/init.lua create mode 100644 plugin/tiny-lightbulb.lua create mode 100644 scripts/minimal_init.lua create mode 100755 scripts/test.sh create mode 100644 selene.toml create mode 100644 stylua.toml create mode 100644 tests/test_init.lua create mode 100644 vim.toml diff --git a/.github/FUNDING.yml b/.github/FUNDING.yml new file mode 100644 index 0000000..d2b37e7 --- /dev/null +++ b/.github/FUNDING.yml @@ -0,0 +1 @@ +github: swaits diff --git a/.github/ISSUE_TEMPLATE/bug_report.md b/.github/ISSUE_TEMPLATE/bug_report.md new file mode 100644 index 0000000..1865fe3 --- /dev/null +++ b/.github/ISSUE_TEMPLATE/bug_report.md @@ -0,0 +1,21 @@ +--- +name: Bug report +about: Create a report to help us improve +title: '' +labels: bug +assignees: '' +--- + +**Describe the bug** +A clear and concise description of what the bug is. + +**To Reproduce** +Steps to reproduce the behavior. + +**Expected behavior** +A clear and concise description of what you expected to happen. + +**Environment** +- Neovim version: +- OS: +- Config (if relevant): diff --git a/.github/ISSUE_TEMPLATE/feature_request.md b/.github/ISSUE_TEMPLATE/feature_request.md new file mode 100644 index 0000000..8b76998 --- /dev/null +++ b/.github/ISSUE_TEMPLATE/feature_request.md @@ -0,0 +1,16 @@ +--- +name: Feature request +about: Suggest an idea for this project +title: '' +labels: enhancement +assignees: '' +--- + +**Is your feature request related to a problem?** +A clear and concise description of what the problem is. + +**Describe the solution you'd like** +A clear and concise description of what you want to happen. + +**Additional context** +Add any other context about the feature request here. diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml new file mode 100644 index 0000000..dea83c1 --- /dev/null +++ b/.github/workflows/ci.yml @@ -0,0 +1,30 @@ +name: CI +on: + push: + branches: [main] + pull_request: + branches: [main] + +jobs: + lint: + runs-on: ubuntu-latest + steps: + - uses: actions/checkout@v4 + - uses: JohnnyMorganz/stylua-action@v4 + with: + token: ${{ secrets.GITHUB_TOKEN }} + version: latest + args: --check lua/ plugin/ + + test: + runs-on: ubuntu-latest + steps: + - uses: actions/checkout@v4 + - name: Install Neovim + run: | + wget -q https://github.com/neovim/neovim/releases/download/v0.12.0/nvim-linux-x86_64.appimage -O /tmp/nvim.appimage + chmod +x /tmp/nvim.appimage + /tmp/nvim.appimage --appimage-extract > /dev/null 2>&1 + echo "$PWD/squashfs-root/usr/bin" >> $GITHUB_PATH + - name: Run tests + run: ./scripts/test.sh diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..009393c --- /dev/null +++ b/.gitignore @@ -0,0 +1,6 @@ +*.swp +*.swo +*~ +.DS_Store +tags +deps/ diff --git a/CHANGELOG.md b/CHANGELOG.md new file mode 100644 index 0000000..802aa0b --- /dev/null +++ b/CHANGELOG.md @@ -0,0 +1,18 @@ +# Changelog + +All notable changes to this project will be documented in this file. + +The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.1.0/), +and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html). + +## [0.1.0] - 2026-04-09 + +### Added + +- Initial release +- Lightbulb sign when LSP code actions are available on current line +- Async LSP requests with generation-counter stale-response handling +- Configurable sign text, highlight group, and extmark priority +- Zero-config auto-loading via `plugin/` directory + +[0.1.0]: https://github.com/swaits/tiny-lightbulb.nvim/releases/tag/v0.1.0 diff --git a/LICENSE b/LICENSE new file mode 100644 index 0000000..1c20050 --- /dev/null +++ b/LICENSE @@ -0,0 +1,21 @@ +MIT License + +Copyright (c) 2026 Stephen Waits + +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..4160468 --- /dev/null +++ b/README.md @@ -0,0 +1,52 @@ +# tiny-lightbulb.nvim + +A tiny lightbulb for Neovim 0.12+. Shows a sign when LSP code actions are +available on the current line. + +Zero config. ~50 lines of Lua. + +## Features + +- Displays a 💡 in the sign column when code actions are available +- Clears instantly on cursor move +- Async LSP requests with stale-response handling +- Configurable sign text, highlight, and priority +- Works out of the box — no setup call required + +## Requirements + +- Neovim >= 0.12 +- At least one LSP server that supports code actions + +## Installation + +Add to your `vim.pack.add()` call: + +```lua +"https://github.com/swaits/tiny-lightbulb.nvim", +``` + +Done. The plugin auto-loads with sensible defaults. + +## Configuration + +All options are optional. Call `setup()` only if you want to customize: + +```lua +require("tiny-lightbulb").setup({ + sign = "💡", -- sign column text + hl = "DiagnosticWarn", -- highlight group + priority = 10, -- extmark priority +}) +``` + +## How it works + +On `CursorHold`, the plugin asks all attached LSP clients whether code +actions exist for the current line. If any are returned, an extmark sign +is placed. On `CursorMoved`, the sign is cleared and any in-flight LSP +responses are discarded via a generation counter. + +## License + +[MIT](LICENSE) diff --git a/doc/tiny-lightbulb.txt b/doc/tiny-lightbulb.txt new file mode 100644 index 0000000..c065b23 --- /dev/null +++ b/doc/tiny-lightbulb.txt @@ -0,0 +1,68 @@ +*tiny-lightbulb.txt* Show a sign when LSP code actions are available + +Author: Stephen Waits +License: MIT + +============================================================================== +CONTENTS *tiny-lightbulb-contents* + + 1. Introduction ............................ |tiny-lightbulb-introduction| + 2. Requirements ............................ |tiny-lightbulb-requirements| + 3. Installation ............................ |tiny-lightbulb-installation| + 4. Configuration ........................... |tiny-lightbulb-configuration| + +============================================================================== +1. INTRODUCTION *tiny-lightbulb-introduction* + +tiny-lightbulb.nvim displays a sign in the sign column when LSP code actions +are available for the current line. Works out of the box with zero +configuration — just install and go. + +============================================================================== +2. REQUIREMENTS *tiny-lightbulb-requirements* + +- Neovim >= 0.12 +- At least one LSP server that supports code actions + +============================================================================== +3. INSTALLATION *tiny-lightbulb-installation* + +Add to your |vim.pack.add()| call: >lua + + "https://github.com/swaits/tiny-lightbulb.nvim", +< +That's it. The plugin auto-loads with defaults. To customize, call setup: >lua + + require("tiny-lightbulb").setup({ + sign = "💡", + hl = "DiagnosticWarn", + priority = 10, + }) +< +============================================================================== +4. CONFIGURATION *tiny-lightbulb-configuration* + + *tiny-lightbulb.setup()* +setup({opts}) + + Optional. The plugin works with defaults if you never call this. + Calling setup reconfigures the plugin with the given options. + + All options shown with defaults: >lua + + require("tiny-lightbulb").setup({ + sign = "💡", -- sign column text + hl = "DiagnosticWarn", -- highlight group for the sign + priority = 10, -- extmark priority + }) +< + Parameters: ~ + {opts} (table|nil) Configuration table + + Fields: ~ + {sign} (string) Text shown in the sign column. Default: "💡" + {hl} (string) Highlight group for the sign. Default: "DiagnosticWarn" + {priority} (number) Extmark priority. Default: 10 + +============================================================================== +vim:tw=78:ts=8:ft=help:norl: diff --git a/lua/tiny-lightbulb/init.lua b/lua/tiny-lightbulb/init.lua new file mode 100644 index 0000000..5bfb84f --- /dev/null +++ b/lua/tiny-lightbulb/init.lua @@ -0,0 +1,55 @@ +local M = {} + +function M.setup(opts) + opts = vim.tbl_extend("force", { + sign = "💡", + hl = "DiagnosticWarn", + priority = 10, + }, opts or {}) + + local ns = vim.api.nvim_create_namespace("tiny-lightbulb") + local gen = 0 + + local function clear() + gen = gen + 1 + vim.api.nvim_buf_clear_namespace(0, ns, 0, -1) + end + + vim.api.nvim_create_autocmd({ "CursorHold", "CursorHoldI" }, { + group = vim.api.nvim_create_augroup("tiny-lightbulb", { clear = true }), + callback = function() + local buf, lnum = vim.api.nvim_get_current_buf(), vim.fn.line(".") - 1 + clear() + if #vim.lsp.get_clients({ bufnr = buf, method = "textDocument/codeAction" }) == 0 then + return + end + local id = gen + vim.lsp.buf_request(buf, "textDocument/codeAction", { + textDocument = vim.lsp.util.make_text_document_params(buf), + range = { start = { line = lnum, character = 0 }, ["end"] = { line = lnum, character = 0 } }, + context = { diagnostics = vim.diagnostic.get(buf, { lnum = lnum }) }, + }, function(err, results) + if + id == gen + and not err + and results + and #results > 0 + and vim.api.nvim_buf_is_valid(buf) + then + vim.api.nvim_buf_set_extmark(buf, ns, lnum, 0, { + sign_text = opts.sign, + sign_hl_group = opts.hl, + priority = opts.priority, + }) + end + end) + end, + }) + + vim.api.nvim_create_autocmd({ "CursorMoved", "CursorMovedI", "InsertEnter" }, { + group = vim.api.nvim_create_augroup("tiny-lightbulb-clear", { clear = true }), + callback = clear, + }) +end + +return M diff --git a/plugin/tiny-lightbulb.lua b/plugin/tiny-lightbulb.lua new file mode 100644 index 0000000..f8b523d --- /dev/null +++ b/plugin/tiny-lightbulb.lua @@ -0,0 +1 @@ +require("tiny-lightbulb").setup() diff --git a/scripts/minimal_init.lua b/scripts/minimal_init.lua new file mode 100644 index 0000000..3be968c --- /dev/null +++ b/scripts/minimal_init.lua @@ -0,0 +1,4 @@ +-- Minimal init for testing tiny-lightbulb.nvim +vim.opt.rtp:prepend(".") +vim.opt.rtp:prepend("deps/mini.nvim") +vim.cmd("runtime plugin/tiny-lightbulb.lua") diff --git a/scripts/test.sh b/scripts/test.sh new file mode 100755 index 0000000..f54b001 --- /dev/null +++ b/scripts/test.sh @@ -0,0 +1,11 @@ +#!/usr/bin/env bash +set -euo pipefail + +# Clone mini.nvim for test framework (if not cached) +if [ ! -d deps/mini.nvim ]; then + mkdir -p deps + git clone --depth 1 https://github.com/echasnovski/mini.nvim deps/mini.nvim +fi + +# Run tests +nvim --headless -u scripts/minimal_init.lua -c "lua require('mini.test').setup(); MiniTest.run_file('tests/test_init.lua')" diff --git a/selene.toml b/selene.toml new file mode 100644 index 0000000..d03ab0a --- /dev/null +++ b/selene.toml @@ -0,0 +1 @@ +std = "vim" diff --git a/stylua.toml b/stylua.toml new file mode 100644 index 0000000..9218af5 --- /dev/null +++ b/stylua.toml @@ -0,0 +1,4 @@ +column_width = 100 +indent_type = "Spaces" +indent_width = 2 +quote_style = "AutoPreferDouble" diff --git a/tests/test_init.lua b/tests/test_init.lua new file mode 100644 index 0000000..d2e1ccb --- /dev/null +++ b/tests/test_init.lua @@ -0,0 +1,36 @@ +local T = MiniTest.new_set() +local eq = MiniTest.expect.equality + +-- ── Setup tests ─────────────────────────────────────────────────────── + +T["setup"] = MiniTest.new_set() + +T["setup"]["creates augroups"] = function() + -- plugin/ auto-loaded via minimal_init, so augroups should exist + local groups = vim.api.nvim_get_autocmds({ group = "tiny-lightbulb" }) + eq(#groups > 0, true) + local clear_groups = vim.api.nvim_get_autocmds({ group = "tiny-lightbulb-clear" }) + eq(#clear_groups > 0, true) +end + +T["setup"]["is idempotent"] = function() + require("tiny-lightbulb").setup() + require("tiny-lightbulb").setup() + local groups = vim.api.nvim_get_autocmds({ group = "tiny-lightbulb" }) + eq(#groups > 0, true) +end + +T["setup"]["respects custom sign"] = function() + -- Just verify it doesn't error with custom opts + require("tiny-lightbulb").setup({ sign = "CA" }) + local groups = vim.api.nvim_get_autocmds({ group = "tiny-lightbulb" }) + eq(#groups > 0, true) +end + +T["setup"]["respects custom highlight"] = function() + require("tiny-lightbulb").setup({ hl = "DiagnosticInfo" }) + local groups = vim.api.nvim_get_autocmds({ group = "tiny-lightbulb" }) + eq(#groups > 0, true) +end + +return T diff --git a/vim.toml b/vim.toml new file mode 100644 index 0000000..0fa5c4f --- /dev/null +++ b/vim.toml @@ -0,0 +1,2 @@ +[vim] +any = true