commit a41b935da1fc82829c221079566b4d28d903962f Author: Stephen Waits Date: Thu Apr 9 17:30:31 2026 -0600 feat: initial release 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..7f14669 --- /dev/null +++ b/CHANGELOG.md @@ -0,0 +1,14 @@ +# 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 + +[0.1.0]: https://github.com/swaits/tiny-spark.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..4a5cdf7 --- /dev/null +++ b/README.md @@ -0,0 +1,51 @@ +# tiny-spark.nvim + +A tiny cursor jump animation for Neovim 0.12+. Flashes a brief highlight +at the cursor position after large jumps. + +Zero config. ~55 lines of Lua. + +## Features + +- Brief flash at cursor position after jumping 5+ lines +- Smooth fade-out animation via `winblend` +- Configurable threshold, duration, width, and highlight group +- Extremely lightweight — no dependencies, single timer +- Works out of the box — no setup call required + +## Requirements + +- Neovim >= 0.12 +- A terminal that supports `winblend` (most modern terminals) + +## Installation + +Add to your `vim.pack.add()` call: + +```lua +"https://github.com/swaits/tiny-spark.nvim", +``` + +## Configuration + +All options are optional. Call `setup()` only if you want to customize: + +```lua +require("tiny-spark").setup({ + threshold = 5, -- minimum line distance to trigger + duration = 150, -- fade duration in ms + width = 8, -- flash width in columns + hl = "CurSearch", -- highlight group +}) +``` + +## How it works + +On `CursorMoved`, the plugin compares the current cursor line to the +previous one. If the distance exceeds the threshold, it opens a small +floating window at the cursor position and fades it out by increasing +`winblend` from 0 to 100 over the configured duration. + +## License + +[MIT](LICENSE) diff --git a/doc/tiny-spark.txt b/doc/tiny-spark.txt new file mode 100644 index 0000000..7754e21 --- /dev/null +++ b/doc/tiny-spark.txt @@ -0,0 +1,40 @@ +*tiny-spark.txt* Cursor jump flash animation + +Author: Stephen Waits +License: MIT + +============================================================================== +CONTENTS *tiny-spark-contents* + + 1. Introduction ............................ |tiny-spark-introduction| + 2. Configuration ........................... |tiny-spark-configuration| + +============================================================================== +1. INTRODUCTION *tiny-spark-introduction* + +tiny-spark.nvim flashes a brief highlight at the cursor position after +large jumps, making it easy for your eye to follow the cursor. + +============================================================================== +2. CONFIGURATION *tiny-spark-configuration* + + *tiny-spark.setup()* +setup({opts}) + + Optional. All options shown with defaults: >lua + + require("tiny-spark").setup({ + threshold = 5, -- minimum line distance to trigger + duration = 150, -- fade duration in ms + width = 8, -- flash width in columns + hl = "CurSearch", -- highlight group + }) +< + Fields: ~ + {threshold} (number) Minimum line jump to trigger flash. Default: 5 + {duration} (number) Fade-out duration in milliseconds. Default: 150 + {width} (number) Width of flash in columns. Default: 8 + {hl} (string) Highlight group. Default: "CurSearch" + +============================================================================== +vim:tw=78:ts=8:ft=help:norl: diff --git a/lua/tiny-spark/init.lua b/lua/tiny-spark/init.lua new file mode 100644 index 0000000..a4fcd16 --- /dev/null +++ b/lua/tiny-spark/init.lua @@ -0,0 +1,92 @@ +local M = {} + +function M.setup(opts) + opts = opts or {} + local threshold = opts.threshold or 3 + local duration = opts.duration or 50 + local step = math.ceil(1000 / 60) + local prev = { 0, 0 } + local ns = vim.api.nvim_create_namespace("tiny-spark") + + local trail_chars = opts.chars or { "░", "▒", "▓", "█" } + local palette = opts.palette + or { + { fg = "#552200" }, + { fg = "#cc4400" }, + { fg = "#ff8822" }, + { fg = "#ffcc44" }, + { fg = "#ffffff", bold = true }, + } + for i, def in ipairs(palette) do + vim.api.nvim_set_hl(0, "TinySpark" .. i, def) + end + + vim.api.nvim_create_autocmd("CursorMoved", { + group = vim.api.nvim_create_augroup("tiny-spark", { clear = true }), + callback = function() + local cur = vim.api.nvim_win_get_cursor(0) + local dy = cur[1] - prev[1] + local dx = cur[2] - prev[2] + local dist = math.abs(dy) + local old_prev = { prev[1], prev[2] } + prev = cur + if dist < threshold then + return + end + + local buf = vim.api.nvim_get_current_buf() + local line_count = vim.api.nvim_buf_line_count(buf) + local dir = dy > 0 and 1 or -1 + + -- Build trail from origin to destination + local trail = {} + for i = 0, dist - 1 do + local t = dist > 1 and i / (dist - 1) or 1 + local row = old_prev[1] - 1 + dir * i + local col = old_prev[2] + math.floor(dx * t + 0.5) + if row >= 0 and row < line_count and col >= 0 then + table.insert(trail, { row = row, col = col }) + end + end + + local elapsed = 0 + local timer = vim.uv.new_timer() + timer:start( + 0, + step, + vim.schedule_wrap(function() + elapsed = elapsed + step + vim.api.nvim_buf_clear_namespace(buf, ns, 0, -1) + + if elapsed >= duration or not vim.api.nvim_buf_is_valid(buf) or #trail == 0 then + timer:stop() + if not timer:is_closing() then + timer:close() + end + return + end + + -- Shrink trail from origin end (remove the dark/far end first) + local progress = elapsed / duration + local drop = math.floor(#trail * progress) + local start = drop + 1 + + for i = start, #trail do + local p = trail[i] + -- t=0 at start of visible trail (dimmest), t=1 at destination (brightest) + local t = (#trail - start) > 0 and (i - start) / (#trail - start) or 1 + local char_idx = math.max(1, math.min(#trail_chars, math.ceil(t * #trail_chars))) + local hl_idx = math.max(1, math.min(#palette, math.ceil(t * #palette))) + pcall(vim.api.nvim_buf_set_extmark, buf, ns, p.row, 0, { + virt_text = { { trail_chars[char_idx], "TinySpark" .. hl_idx } }, + virt_text_pos = "overlay", + virt_text_win_col = p.col, + }) + end + end) + ) + end, + }) +end + +return M diff --git a/plugin/tiny-spark.lua b/plugin/tiny-spark.lua new file mode 100644 index 0000000..92ea981 --- /dev/null +++ b/plugin/tiny-spark.lua @@ -0,0 +1 @@ +require("tiny-spark").setup() diff --git a/scripts/minimal_init.lua b/scripts/minimal_init.lua new file mode 100644 index 0000000..176182b --- /dev/null +++ b/scripts/minimal_init.lua @@ -0,0 +1,4 @@ +-- Minimal init for testing tiny-spark.nvim +vim.opt.rtp:prepend(".") +vim.opt.rtp:prepend("deps/mini.nvim") +vim.cmd("runtime plugin/tiny-spark.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..1916b6b --- /dev/null +++ b/tests/test_init.lua @@ -0,0 +1,24 @@ +local T = MiniTest.new_set() +local eq = MiniTest.expect.equality + +T["setup"] = MiniTest.new_set() + +T["setup"]["creates augroup"] = function() + local groups = vim.api.nvim_get_autocmds({ group = "tiny-spark" }) + eq(#groups > 0, true) +end + +T["setup"]["is idempotent"] = function() + require("tiny-spark").setup() + require("tiny-spark").setup() + local groups = vim.api.nvim_get_autocmds({ group = "tiny-spark" }) + eq(#groups > 0, true) +end + +T["setup"]["respects custom threshold"] = function() + require("tiny-spark").setup({ threshold = 10 }) + local groups = vim.api.nvim_get_autocmds({ group = "tiny-spark" }) + 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