- Lua 100%
|
|
||
|---|---|---|
| .github | ||
| doc | ||
| lua/haunt | ||
| plugin | ||
| scripts | ||
| tests | ||
| .gitignore | ||
| CHANGELOG.md | ||
| CONTRIBUTING.md | ||
| LICENSE | ||
| README.md | ||
| stylua.toml | ||
haunt.nvim 👻
Showcase
|
General Usage |
Picker |
|
Sidekick Integration |
Git Branch Scope |
Hear the ghosts tell you where you were, and why you were there.
Bring back the past with haunt.nvim!
Annotate your codebase with ghost text. Search through the history that you choose.
Keep your mental overhead to a minimum and never repeatedly rummage through your codebase again.
Features
- Virtual text annotations
- Keep your personal notes in your code without modifying the actual files
- Git integration
- annotations are tied to a git branch. Keep different notes for different branches
- Jump around using your hauntings
- Search through your bookmarks with
snacks.nvimortelescope.nvim - Use
sidekick.nvimto send your annotations to your favorite cli tool. Have a robot purge you of your hauntings! - Populate the quickfix list with your bookmarks when you want a simple jump list
- Though the examples don't show this, this plugin was deisgned to ease the navigation of massive codebases through semantic markings
- Super fast, and does its best to have as little computations, and load time, as possible. I get around .6ms ;)
Requirements
- Neovim 0.11 - virtual text
- snacks.nvim - picker integration. (optional)
- telescope.nvim - picker integration. (optional)
- fzf-lua - picker integration. (optional)
- sidekick.nvim - 'AI' integration (and a cli tool of your choice). (optional)
Installation
return {
"TheNoeTrevino/haunt.nvim",
-- default config: change to your liking, or remove it to use defaults
---@class HauntConfig
opts = {
sign = "",
sign_hl = "DiagnosticInfo",
virt_text_hl = "HauntAnnotation", -- links to DiagnosticVirtualTextHint
annotation_prefix = " ",
annotation_suffix = "",
line_hl = nil,
virt_text_pos = "eol",
above_max_width = 80,
above_border = "rounded", -- "single", "double", "none", or character array
data_dir = nil,
per_branch_bookmarks = true,
picker = "auto", -- "auto", "snacks", "telescope", or "fzf"
picker_keys = { -- picker agnostic, we got you covered
delete = { key = "d", mode = { "n" } },
edit_annotation = { key = "a", mode = { "n" } },
},
},
-- recommended keymaps, with a helpful prefix alias
init = function()
local haunt = require("haunt.api")
local haunt_picker = require("haunt.picker")
local map = vim.keymap.set
local prefix = "<leader>h"
-- annotations
map("n", prefix .. "a", function()
haunt.annotate()
end, { desc = "Annotate" })
map("n", prefix .. "t", function()
haunt.toggle_annotation()
end, { desc = "Toggle annotation" })
map("n", prefix .. "T", function()
haunt.toggle_all_lines()
end, { desc = "Toggle all annotations" })
map("n", prefix .. "d", function()
haunt.delete()
end, { desc = "Delete bookmark" })
map("n", prefix .. "C", function()
haunt.clear_all()
end, { desc = "Delete all bookmarks" })
-- move
map("n", prefix .. "p", function()
haunt.prev()
end, { desc = "Previous bookmark" })
map("n", prefix .. "n", function()
haunt.next()
end, { desc = "Next bookmark" })
-- picker
map("n", prefix .. "l", function()
haunt_picker.show()
end, { desc = "Show Picker" })
-- quickfix
map("n", prefix .. "q", function()
haunt.to_quickfix()
end, { desc = "Send Hauntings to QF Lix (buffer)" })
map("n", prefix .. "Q", function()
haunt.to_quickfix({ current_buffer = true })
end, { desc = "Send Hauntings to QF Lix (all)" })
-- yank
map("n", prefix .. "y", function()
haunt.yank_locations({current_buffer = true})
end, { desc = "Send Hauntings to Clipboard (buffer)" })
map("n", prefix .. "Y", function()
haunt.yank_locations()
end, { desc = "Send Hauntings to Clipboard (all)" })
end,
}
Above Annotations
Click to expand
Set virt_text_pos = "above" to display annotations in a bordered box above the line instead of inline. The box wraps long text and adapts to the window width.
above_max_width— maximum box width in columns (default80, clamped to window width)above_border— border style:"rounded"(default),"single","double","none", or a character array like{"╭", "─", "╮", "│", "╯", "─", "╰", "│"}
Usage
API
By default, haunt.nvim provides no default keymaps. You will have to set them up yourself. See the installation section for an example. The installation section includes some recommended keymaps to get you started. You can also just use the user commands, which we will talk about later.
Here are the exposed API functions you should know about:
local haunt = require("haunt.api")
local haunt_picker = require("haunt.picker")
local haunt_sk = require("haunt.sidekick")
-- See `:h haunt-api` for more info on each function
-- Annotate the current line with a ghost text annotation,
-- or edit the annotation if it already exists
haunt.annotate()
-- Toggle visibility of the current annotation
haunt.toggle_annotation()
-- Toggle visibility of the all annotations
haunt.toggle_all_lines()
-- Remove all annotations in the workspace. Good for when you finish up a subtask
haunt.clear_all()
-- Delete annotation on the current line
haunt.delete()
-- Jump to the next/prev annotation in the buffer
haunt.next()
haunt.prev()
-- Open the bookmark picker.
--
-- Displays all bookmarks in an interactive picker.
-- Supports Snacks.nvim, Telescope.nvim, and fzf-lua (configurable via `picker` option).
-- Allows jumping to, deleting, or editing bookmark annotations.
-- see :h haunt-picker for more info
haunt_picker.show()
haunt_picker.show({ layout = { preset = "vscode" } }) -- Snacks options
haunt_picker.show({ prompt_title = "My Bookmarks" }) -- Telescope options
haunt_picker.show({ prompt = "Bookmarks> " }) -- fzf-lua options
-- Get bookmark locations formatted for sidekick.nvim.
-- Returns bookmarks in sidekick-compatible format:
-- `- @/{path} :L{line} - "{note}"`
-- see :h haunt-sidekick for more info, and the sidekick section below
haunt_sk.get_locations()
haunt_sk.get_locations({current_buffer = true})
--- Change the data directory and reload all bookmarks.
---
--- Saves current bookmarks to the old data_dir, clears all visual elements,
--- then loads bookmarks from the new location and restores visuals.
--- This is useful for autocommands that need to switch bookmark contexts.
haunt.change_data_dir("~/projects/myproject/.bookmarks/")
haunt.change_data_dir(nil) -- reset to default
User Commands
Or you can use the user commands:
HauntAnnotate
HauntClear
HauntClearAll
HauntDelete
HauntList
HauntNext
HauntPrev
HauntQf
HauntQfAll
HauntChangeDataDir [path]
HauntMigrate
HauntReload
Take a look at :h haunt-commands for more details on each command, and examples of how to use them.
If you wanna script something with this plugin, take a look at :h haunt.
I tried my best to expose as many useful functions as possible.
Integrations
Picker (snacks.nvim / telescope.nvim / fzf-lua)
Click to expand
Search, edit, and delete annotations from the picker using snacks.nvim, telescope.nvim, or fzf-lua.
By default, haunt.nvim uses "auto" mode which tries Snacks first, then Telescope, then fzf-lua, then falls back to vim.ui.select.
You can explicitly choose your picker:
return {
"TheNoeTrevino/haunt.nvim",
opts = {
-- Choose your picker: "auto", "snacks", "telescope", or "fzf"
picker = "auto",
-- Customize picker keybindings (works for both Snacks and Telescope)
picker_keys = {
delete = {
key = "d",
mode = { "n" },
},
edit_annotation = {
key = "a",
mode = { "n" },
},
},
},
}
Picker actions:
<CR>: Jump to the selected bookmarkd(normal mode): Delete the selected bookmarka(normal mode): Edit the bookmark's annotation
sidekick.nvim
Click to expand
Send the position of your annotations to your favorite CLI tool through sidekick
Add this to your sidekick configuration:
local haunt_sk = require("haunt.sidekick")
return {
"folke/sidekick.nvim",
cmd = "Sidekick",
---@class sidekick.Config
opts = {
cli = {
prompts = {
haunt_all = function()
return haunt_sk.get_locations()
end,
haunt_buffer = function()
return haunt_sk.get_locations({ current_buffer = true })
end,
},
}
}
}
Persistence, Sharing, and Migration
Bookmarks are saved as one JSON file per project, per branch, keyed by your git root commit, with project-relative paths inside. This makes the file portable across machines, forks, and checkouts.
Here are some ideas for how to use this for inspiration:
- Store haunt annotation on a NAS and load them across machines (you can use tailscale!!)
- Share with teammates by committing the bookmark files to git, and pointing everyone to the same
data_dir- data can be
vim.fn.getcwd() .. "/.haunt/"
- data can be
For more details, see the help docs:
:h haunt-persistence— storage format and project keying:h haunt-sharing— share with teammates via git, or sync privately across your own machines (NAS, Tailscale, private repo):h haunt-migration— upgrading from the v1 format
The goal of this was to be flexible enough to support a variety of workflows, while still being simple and intuitive to use out of the box.
Have fun!
Project-Specific Bookmarks
Use change_data_dir to scope bookmarks per project/directory:
vim.api.nvim_create_autocmd("DirChanged", {
callback = function()
local project_bookmarks = vim.fn.getcwd() .. "/.bookmarks/"
require("haunt.api").change_data_dir(project_bookmarks)
end,
})
Why?
I have tried all the bookmarking plugins out there, and none of them really fit my workflow. The specific issues I kept having were:
- Why is there a mark/bookmark here? Did I do that on purpose? Oh whatever...
- I wish I could fuzzy search the semantic meaning of the mark that I would have in my head.
- I want to send the marks, with my annotations, to my AI assistant to help me with my daily workflow.
- On massive codebases, I wish these marks had a 'why' to them.
The closest alternative I found was vim-bookmarks, but it is semi-broken, and the last commit was 5 years ago. Time for modern alternative!
I hope this helps others with the same issues.
Acknowledgements
- folke for snacks.nvim and sidekick.nvim, the API was extremely easy to work with
nvim-telescopeteam for telescope.nvimmini.nvimfor themini.docstemplate