Marks not cutting it? Create and manage bookmarks more easily, with an easy to use and configurable UI.
Programming often involves navigating between similar points of interest. Additionally, layers of functionality are often composed together, and thus are often read and edited as part of a stack. spelunk.nvim
leans into this mental model to allow you to manage bookmarks as related stacks.
spelunk.nvim
also seeks to take an opinionated approach to configuration. Default keymaps are provided to give the full experience out of the box, as opposed to a build-your-own, API-centric approach. API documentation is provided for those who would prefer to customize their experience.
See the faqs/
directory for more custom setup guides.
Neovim (stable only) >= 0.10.0
Via lazy:
require('lazy').setup({
{
'EvWilson/spelunk.nvim',
dependencies = {
'nvim-lua/plenary.nvim', -- For window drawing utilities
'nvim-telescope/telescope.nvim', -- Optional: for fuzzy search capabilities
'nvim-treesitter/nvim-treesitter', -- Optional: for showing grammar context
},
config = function()
require('spelunk').setup({
enable_persist = true
})
end
}
})
Want to configure more keybinds? Pass a config object to the setup function. Here's the default mapping object for reference:
{
base_mappings = {
-- Toggle the UI open/closed
toggle = '<leader>bt',
-- Add a bookmark to the current stack
add = '<leader>ba',
-- Move to the next bookmark in the stack
next_bookmark = '<leader>bn',
-- Move to the previous bookmark in the stack
prev_bookmark = '<leader>bp',
-- Fuzzy-find all bookmarks
search_bookmarks = '<leader>bf',
-- Fuzzy-find bookmarks in current stack
search_current_bookmarks = '<leader>bc',
-- Fuzzy find all stacks
search_stacks = '<leader>bs',
},
window_mappings = {
-- Move the UI cursor down
cursor_down = 'j',
-- Move the UI cursor up
cursor_up = 'k',
-- Move the current bookmark down in the stack
bookmark_down = '<C-j>',
-- Move the current bookmark up in the stack
bookmark_up = '<C-k>',
-- Jump to the selected bookmark
goto_bookmark = '<CR>',
-- Jump to the selected bookmark in a new vertical split
goto_bookmark_hsplit = 'x',
-- Jump to the selected bookmark in a new horizontal split
goto_bookmark_vsplit = 'v',
-- Delete the selected bookmark
delete_bookmark = 'd',
-- Navigate to the next stack
next_stack = '<Tab>',
-- Navigate to the previous stack
previous_stack = '<S-Tab>',
-- Create a new stack
new_stack = 'n',
-- Delete the current stack
delete_stack = 'D',
-- Rename the current stack
edit_stack = 'E',
-- Close the UI
close = 'q',
-- Open the help menu
help = 'h',
},
-- Flag to enable directory-scoped bookmark persistence
enable_persist = false,
-- Prefix for the Lualine integration
-- (Change this if your terminal emulator lacks emoji support)
statusline_prefix = '🔖',
-- Set UI orientation
-- Type: 'vertical' | 'horizontal' | LayoutProvider
-- Advanced customization: you may set your own layout provider for fine-grained control over layout
-- See `types.lua` and `layout.lua` for guidance on setting this up
orientation = 'vertical',
-- Enable to show mark index in status column
enable_status_col_display = false,
-- The character rendered before the currently selected bookmark in the UI
cursor_character = '>',
}
Want to not set a keybind for a given action? Set the bound value to 'NONE'
and it will skip being set.
Keybinds can be set to either individual strings or arrays of strings, so for example window_mappings.cursor_down
above could be mapped to { 'j', '<leader>s' }
.
Check the mentioned help screen to see current keybinds and their use:
A default integration with lualine is provided to show the number of active bookmarks in the current buffer. You may override the prefix for this string in the config map above. The result of the provided configuration here can be seen in the demo video above, in the bottom left of the screen.
{
'nvim-lualine/lualine.nvim',
config = function()
require('lualine').setup {
sections = {
lualine_b = { 'spelunk' },
-- Or, added to the default lualine_b config from here: https://github.com/nvim-lualine/lualine.nvim?tab=readme-ov-file#default-configuration
-- lualine_b = { 'branch', 'diff', 'diagnostics', 'spelunk' },
},
}
end
},
Here be dragons! This plugin is designed with the default bindings in mind, so there is potential for misuse here. This list will be non-exhaustive to cover just the most useful available functions, with the least potential for sharp edges.
All functions listed can be called like such from within Neovim Lua code:
require('spelunk').setup(opts)
If there is functionality you'd like to see added or exposed, please feel free to open an issue!
filename_formatter
fun(abspath: string): string
function(abspath)
return vim.fn.fnamemodify(abspath, ':~:.')
end
display_function
fun(mark: VirtualBookmark | PhysicalBookmark | FullBookmark): string
M.display_function = function(mark)
return string.format('%s:%d', M.filename_formatter(mark.file), mark.line)
end
setup(config)
config: table
: a table in the format given in the above Configuration sectiontoggle_window()
close_windows()
add_bookmark()
move_cursor(direction)
direction: integer
(1 | -1): direction to move the cursor, 1 is down, -1 is upmove_bookmark(direction)
direction: integer
(1 | -1): direction to move the bookmark, 1 is down, -1 is upgoto_selected_bookmark()
goto_selected_bookmark_horizontal_split()
goto_selected_bookmark_vertical_split()
goto_bookmark_at_index(idx)
idx: integer
: index of the bookmark to navigate todelete_selected_bookmark()
select_and_goto_bookmark(direction)
direction: integer
(1 | -1): direction to move the cursor, 1 is down, -1 is updelete_current_stack()
edit_current_stack()
next_stack()
prev_stack()
new_stack()
search_marks()
search_current_marks()
statusline()
string
all_full_marks()
FullBookmark[]
: see types.luacurrent_full_marks()
FullBookmark[]
qf_all_marks()
qf_current_marks()
add_mark_meta
faqs/
)field: string
: field in the metadata object to assign toval: any
: value to assign to the fieldget_mark_meta
mark: VirtualBookmark | PhysicalBookmark
: the mark to pull the value fromany | nil
require('spelunk.util').get_treesitter_context(mark)
faqs/
for usagemark: VirtualBookmark
string
Some examples are available in th faqs
directory. If there's something you'd like to know how to do, please open an issue and we can see about implementing it!
Currently there are guides on: