Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
27 changes: 23 additions & 4 deletions .github/workflows/refjump.yml
Original file line number Diff line number Diff line change
Expand Up @@ -2,17 +2,36 @@ name: Refjump Workflow
on:
push:
branches: [main]
paths:
- .github/workflows/refjump.yml
- README.md
pull_request:
branches: [main]
permissions:
contents: write
jobs:
test:
name: Run Tests
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- name: Install Neovim
run: |
sudo apt-get update
sudo apt-get install -y software-properties-common
sudo add-apt-repository -y ppa:neovim-ppa/stable
sudo apt-get update
sudo apt-get install -y neovim
- name: Install Plenary
run: |
git clone --depth 1 https://github.com/nvim-lua/plenary.nvim ~/.local/share/nvim/lazy/plenary.nvim
- name: Run Tests
run: |
nvim --headless -u tests/minimal_init.lua -c "PlenaryBustedDirectory tests/ {minimal_init = 'tests/minimal_init.lua'}"
docs:
name: Pandoc to Vimdoc
runs-on: ubuntu-latest
needs: test
if: github.event_name == 'push' && github.ref == 'refs/heads/main'
Copy link
Owner

@mawkler mawkler Jan 5, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Is this conditional neccessary? Isn't this already covered by lines 3-4 in this file?

steps:
- uses: actions/checkout@v2
- uses: actions/checkout@v4
- name: panvimdoc
uses: kdheepak/panvimdoc@main
with:
Expand Down
31 changes: 30 additions & 1 deletion README.md
Original file line number Diff line number Diff line change
Expand Up @@ -42,6 +42,9 @@ opts = {
enable = true, -- Highlight the LSP references on jump
auto_clear = true, -- Automatically clear highlights when cursor moves
},
counter = {
enable = true, -- Show [X/Y] virtual text counter at end of line
},
integrations = {
demicolon = {
enable = true, -- Make `]r`/`[r` repeatable with `;`/`,` using demicolon.nvim
Expand All @@ -53,10 +56,36 @@ opts = {

### Highlights

Refjump highlights the references by default. It uses the highlight group `RefjumpReference`. To change the highlight, see `:help nvim_set_hl()`.
Refjump uses the following highlight groups:

- `RefjumpReference` - for highlighting the references (links to `LspReferenceText` by default)
- `RefjumpCounter` - for the virtual text counter (links to `WarningMsg` by default)

To change the highlights, see `:help nvim_set_hl()`.

## Integrations

### Statusline

Refjump exposes `get_reference_info()` for statusline integration. This returns `{ index = number|nil, total = number }` with the current reference position.

Example for [lualine.nvim](https://github.com/nvim-lualine/lualine.nvim):

```lua
lualine_x = {
{
function()
local refjump = require("refjump")
local info = refjump.get_reference_info()
if info.index then
return string.format("[%d/%d]", info.index, info.total)
end
return ""
end,
},
},
```

### Demicolon

This plugin integrates with [demicolon.nvim](https://github.com/mawkler/demicolon.nvim). Demicolon lets you repeat `]r`/`[r` jumps with `;`/`,` (you can also still repeat `t`/`f`/`T`/`F` like you would expect). Refjump will cache the list of LSP references which gives you super responsive jump repetitions.
Expand Down
48 changes: 48 additions & 0 deletions lua/refjump/counter.lua
Original file line number Diff line number Diff line change
@@ -0,0 +1,48 @@
local M = {}

---Namespace for counter virtual text extmarks
local counter_namespace = vim.api.nvim_create_namespace('RefjumpCounter')

---Name of the highlight group for the counter
local counter_hl_name = 'RefjumpCounter'

---Create highlight group linked to WarningMsg if it doesn't exist
function M.create_hl_group()
local hl = vim.api.nvim_get_hl(0, { name = counter_hl_name })

if vim.tbl_isempty(hl) then
vim.api.nvim_set_hl(0, counter_hl_name, { link = 'WarningMsg' })
end
end

---Show virtual text counter at the end of the current line
---@param current_index integer Current reference index (1-based)
---@param total_count integer Total number of references
---@param bufnr integer Buffer number
function M.show(current_index, total_count, bufnr)
-- Get current cursor position
local cursor = vim.api.nvim_win_get_cursor(0)
local line = cursor[1] - 1 -- Convert to 0-indexed

-- Clear any existing counter in this buffer
M.clear(bufnr)

-- Format the counter text
local text = string.format(' [%d/%d]', current_index, total_count)

-- Add virtual text at end of line
vim.api.nvim_buf_set_extmark(bufnr, counter_namespace, line, 0, {
virt_text = { { text, counter_hl_name } },
virt_text_pos = 'eol',
priority = 100,
})
end

---Clear counter virtual text from buffer
---@param bufnr integer Buffer number (0 for current buffer)
function M.clear(bufnr)
bufnr = bufnr or 0
vim.api.nvim_buf_clear_namespace(bufnr, counter_namespace, 0, -1)
end

return M
8 changes: 8 additions & 0 deletions lua/refjump/highlight.lua
Original file line number Diff line number Diff line change
Expand Up @@ -41,6 +41,12 @@ function M.enable(references, bufnr)
highlight_references = true
end

---Check if reference highlights are currently active
---@return boolean
function M.is_active()
return highlight_references
end

---@deprecated Use `disable()` instead
function M.disable_reference_highlights()
local message = 'refjump.nvim: `disable_reference_highlights()` has been renamed to `disable()`'
Expand All @@ -51,6 +57,8 @@ end
function M.disable()
if not highlight_references then
vim.api.nvim_buf_clear_namespace(0, highlight_namespace, 0, -1)
require('refjump.counter').clear(0)
require('refjump.state').clear()
Copy link
Owner

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The state shouldn't be cleared here, right? If the user just wants to disables the highlights, they might still want to repeat the same reference jump with demicolon.nvim, and so in that case it's still useful to reuse the state

You'll then have to change your statusline example in the README to use the is_active() method to check if the component should be displayed or not

else
highlight_references = false
end
Expand Down
16 changes: 16 additions & 0 deletions lua/refjump/init.lua
Original file line number Diff line number Diff line change
Expand Up @@ -9,12 +9,16 @@ local M = {}
---@field enable? boolean Highlight the LSP references on jump
---@field auto_clear boolean Automatically clear highlights when cursor moves

---@class RefjumpCounterOptions
---@field enable? boolean Show virtual text counter at end of line

---@class RefjumpIntegrationOptions
---@field demicolon? { enable?: boolean } Make `]r`/`[r` repeatable with `;`/`,` using demicolon.nvim

---@class RefjumpOptions
---@field keymaps? RefjumpKeymapOptions
---@field highlights? RefjumpHighlightOptions
---@field counter? RefjumpCounterOptions
---@field integrations? RefjumpIntegrationOptions
---@field verbose? boolean Print message if no reference is found
local options = {
Expand All @@ -27,6 +31,9 @@ local options = {
enable = true,
auto_clear = true,
},
counter = {
enable = true,
},
integrations = {
demicolon = {
enable = true,
Expand Down Expand Up @@ -55,8 +62,17 @@ function M.setup(opts)
require('refjump.highlight').auto_clear_reference_highlights()
end
end

if options.counter.enable then
require('refjump.counter').create_hl_group()
end
end

M.reference_jump = require('refjump.jump').reference_jump

---Get info about current reference position (for statusline use)
---@param bufnr? integer Buffer number (defaults to current buffer)
---@return { index: integer|nil, total: integer }
Comment on lines +73 to +75
Copy link
Owner

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

You don't need to repeat this documentation, it gets reused from the assigned require('refjump.state').get_reference_info, so you can just remove it here

M.get_reference_info = require('refjump.state').get_reference_info

return M
32 changes: 32 additions & 0 deletions lua/refjump/jump.lua
Original file line number Diff line number Diff line change
Expand Up @@ -63,6 +63,29 @@ local function jump_to(next_reference)
vim.cmd('normal! zv')
end

---Find the index of a reference in the references list
---@param reference RefjumpReference
---@param references RefjumpReference[]
---@return integer|nil
Copy link
Owner

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This is a very minor nit, but everywhere where you have type | nil you can replace with type? which is slightly cleaner in my opinion

local function find_reference_index(reference, references)
local idx, _ = vim.iter(references):enumerate():find(function(_, ref)
return ref.range.start.line == reference.range.start.line
and ref.range.start.character == reference.range.start.character
end)
return idx
end

---Display the reference counter if enabled
---@param current_index integer
---@param total_count integer
---@param bufnr integer
local function show_counter(current_index, total_count, bufnr)
if not require('refjump').get_options().counter.enable then
return
end
require('refjump.counter').show(current_index, total_count, bufnr)
end

---@param next_reference RefjumpReference
---@param forward boolean
---@param references RefjumpReference[]
Expand All @@ -74,6 +97,15 @@ local function jump_to_next_reference(next_reference, forward, references)

if next_reference then
jump_to(next_reference)

-- Find current index and update state
local current_index = find_reference_index(next_reference, references)
local bufnr = vim.api.nvim_get_current_buf()

if current_index then
require('refjump.state').set(references, current_index, bufnr)
show_counter(current_index, #references, bufnr)
end
else
vim.notify('refjump.nvim: Could not find the next reference', vim.log.levels.WARN)
end
Expand Down
72 changes: 72 additions & 0 deletions lua/refjump/state.lua
Original file line number Diff line number Diff line change
@@ -0,0 +1,72 @@
local M = {}

---@class RefjumpBufferState
---@field references RefjumpReference[]
---@field current_index integer|nil

---Per-buffer state storage
---@type table<integer, RefjumpBufferState>
local buffer_states = {}

---Get or create state for a buffer
---@param bufnr integer
---@return RefjumpBufferState
local function get_buffer_state(bufnr)
if not buffer_states[bufnr] then
buffer_states[bufnr] = {
references = {},
current_index = nil,
}
end
return buffer_states[bufnr]
end

---Get info about current reference position (for statusline use)
---@param bufnr? integer Buffer number (defaults to current buffer)
---@return { index: integer|nil, total: integer }
function M.get_reference_info(bufnr)
bufnr = bufnr or vim.api.nvim_get_current_buf()
local state = get_buffer_state(bufnr)
return {
index = state.current_index,
total = #state.references,
}
end

---Check if reference navigation is currently active
---@return boolean
function M.is_active()
return require('refjump.highlight').is_active()
end
Comment on lines +36 to +40
Copy link
Owner

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Perhaps this method should be moved to lua/refjump/init.lua instead since this is more of a user facing method


---Update state after jumping to a reference (internal use)
---@param references RefjumpReference[]
---@param current_index integer|nil
---@param bufnr? integer Buffer number (defaults to current buffer)
function M.set(references, current_index, bufnr)
bufnr = bufnr or vim.api.nvim_get_current_buf()
local state = get_buffer_state(bufnr)
state.references = references or {}
state.current_index = current_index
end

---Clear state for a buffer (internal use)
---@param bufnr? integer Buffer number (defaults to current buffer)
function M.clear(bufnr)
bufnr = bufnr or vim.api.nvim_get_current_buf()
buffer_states[bufnr] = nil
end

---Clean up state when buffer is deleted
local function setup_buffer_cleanup()
vim.api.nvim_create_autocmd('BufDelete', {
group = vim.api.nvim_create_augroup('refjump_state_cleanup', { clear = true }),
callback = function(event)
buffer_states[event.buf] = nil
end,
})
end

setup_buffer_cleanup()

return M
17 changes: 17 additions & 0 deletions tests/minimal_init.lua
Original file line number Diff line number Diff line change
@@ -0,0 +1,17 @@
-- Minimal init for running tests
local plenary_path = vim.fn.stdpath('data') .. '/lazy/plenary.nvim'

if vim.fn.isdirectory(plenary_path) == 0 then
vim.fn.system({
'git',
'clone',
'--depth=1',
'https://github.com/nvim-lua/plenary.nvim',
plenary_path,
})
end

vim.opt.rtp:prepend(plenary_path)
vim.opt.rtp:prepend(vim.fn.getcwd())

vim.cmd('runtime plugin/plenary.vim')
Loading