❗ Update: This repository has been archived on 2024.07.28
Create up-to-date harpoon2 information for any place where that information can be useful. For example, in statuslines and the tabline.
harpoonline.mp4
Demo of the features. Using lualine and mini.statusline.
Custom statusline in NvChad v2.5
Note: The video demonstrates the first release and will become outdated.
- Supports multiple harpoon2 lists.
- Highly configurable: Use or modify default formatters or supply a custom formatter
- Decoupled from status-line: Can be used anywhere.
- Resilience: The formatter will return an empty string when harpoon is not present.
- Performance/efficiency: The data is cached and only updated when needed.
Note:
Without caching the info needed from harpoon must be retrieved whenever a status-line updates. Typically, this happens often:
- When navigating inside a buffer
- When editing text inside a buffer
- Latest stable
Neovim
version or nightly - harpoon2
- A statusline. Examples: mini.statusline, lualine, heirline or a custom implementation
Important: don't forget to call require('harpoonline').setup()
to enable the plugin. Without that call, the formatter will return
an empty string.
{
"nvim-lualine/lualine.nvim",
dependencies = { "abeldekat/harpoonline", version = "*" },
config = function()
local Harpoonline = require("harpoonline")
Harpoonline.setup({
on_update = function() require("lualine").refresh() end,
})
local lualine_c = { Harpoonline.format, "filename" }
require("lualine").setup({ sections = { lualine_c = lualine_c } })
end,
}
local function config()
local MiniStatusline = require("mini.statusline")
local HarpoonLine= require("harpoonline")
local function isnt_normal_buffer() return vim.bo.buftype ~= "" end
local function harpoon_highlight() -- using mini.hipatterns
return Harpoonline.is_buffer_harpooned() and "MiniHipatternsHack"
or "MiniStatuslineFilename"
end
local function section_harpoon(args)
if MiniStatusline.is_truncated(args.trunc_width) or isnt_normal_buffer() then
return ""
end
return Harpoonline.format()
end
local function active() -- Hook, see mini.statusline setup
-- copy any lines from mini.statusline, H.default_content_active:
local harpoon_data = section_harpoon({ trunc_width = 75 })
return MiniStatusline.combine_groups({
-- copy any lines from mini.statusline, H.default_content_active:
{ hl = H.harpoon_highlight(), strings = { harpoon_data } },
})
end
HarpoonLine.setup({
on_update = function()
vim.wo.statusline = "%{%v:lua.MiniStatusline.active()%}"
end
})
MiniStatusline.setup({set_vim_settings = false, content = { active = active }})
end
local MiniDeps = require("mini.deps")
local add, now = MiniDeps.add, MiniDeps.now
now(function()
add({
source = "echasnovski/mini.statusline",
depends = {{ source = "abeldekat/harpoonline", checkout = "stable" }}
})
config()
end
A custom setup for mini.statusline can be found in ak.config.ui.mini_statusline
The following configuration is implied when calling setup
without arguments:
---@class HarpoonLineConfig
Harpoonline.config = {
-- other candidates: "", "", "", ""
-- default: icon nf-md-hook in nerdfont, unicode f06e2:
---@type string
icon = '', -- An empty string disables showing the icon
-- Harpoon:list(), when name is nil, retrieves the default list:
-- default_list_name: Configures the display name for the default list.
---@type string
default_list_name = '',
---@type "default" | "short"
formatter = 'default', -- use a built-in formatter
formatter_opts = {
default = {
inactive = ' %s ', -- including spaces
active = '[%s]',
-- Max number of slots to display:
max_slots = 4, -- Suggestion: as many as there are "select" keybindings
-- The number of items in the harpoon list exceeds max_slots:
more = '…', -- horizontal elipsis. Disable using empty string
},
short = {
inner_separator = '|',
},
},
---@type HarpoonlineFormatter
custom_formatter = nil, -- use this formatter when configured
---@type fun()|nil
on_update = nil, -- Recommended: trigger the client when the line has been rebuild.
}
Note: The icon does not display properly in the browser...
Scenario's:
- A: 3 marks, the current buffer is not harpooned
- B: 3 marks, the current buffer is harpooned on mark 2
Note: More examples can be found in ak.config.ui.harpoonline
Default options: config.formatter_opts.default
Output A: 1 2 3
Output B: 1 [2] 3
Note: Five marks, the fifth mark is the active buffer:
Output B: 1 2 3 4 […]
Add to the config: formatter = 'short'
. Default options: config.formatter_opts.short
Output A: [3]
Output B: [2|3]
Harpoonline.setup({
-- config
formatter_opts = {
default = { -- remove all spaces...
inactive = "%s",
active = "[%s]",
},
},
-- more config
})
Output A: 123
Output B: 1[2]3
The following data is kept up-to-date internally, to be processed by formatters:
---@class HarpoonlineData
---@field list_name string|nil -- the name of the current list
---@field items HarpoonItem[] -- the items of the current list
---@field active_idx number|nil -- the harpoon index of the current buffer
Harpoonline.setup({
-- config
---@param data HarpoonlineData
---@param opts HarpoonLineConfig
---@return string
custom_formatter = function(data,opts)
return string.format( -- very short, without the length of the harpoon list
"%s%s%s",
opts.icon .. " ",
data.list_name and string.format("%s ", data.list_name) or "",
data.active_idx and string.format("%d", data.active_idx) or "-"
)
end
-- more config
})
Output A: -
Output B: 2
Harpoonline.setup({
-- config
---@param data HarpoonlineData
---@param opts HarpoonLineConfig
---@return string
custom_formatter = function(data, opts)
local letters = { "j", "k", "l", "h" }
local idx = data.active_idx
local slot = 0
local slots = vim.tbl_map(function(letter)
slot = slot + 1
return idx and idx == slot and string.upper(letter) or letter
end, vim.list_slice(letters, 1, math.min(#letters, #data.items)))
local name = data.list_name and data.list_name or opts.default_list_name
local header = string.format("%s%s%s", opts.icon, name == "" and "" or " ", name)
return header .. " " .. table.concat(slots)
end,
-- more config
})
Output A: jkl
Output B: jKl
Note:
- It is possible to also use inner highlights in the formatter function. See the example recipe for NvChad.
- It is possible to use the
harpoon
information inside eachdata.items
This plugin supports working with multiple harpoon lists. The list in use when Neovim is started is assumed to be the default list
Important:
The plugin needs to be notified when switching to another list
using its custom HarpoonSwitchedList
event:
-- Starts with the default. Use this variable in harpoon:list(list_name)
local list_name = nil
vim.keymap.set("n", "<leader>J", function()
-- toggle between the default list(nil) and list "custom"
list_name = list_name ~= "custom" and "custom" or nil
vim.api.nvim_exec_autocmds("User",
{ pattern = "HarpoonSwitchedList", modeline = false, data = list_name })
end, { desc = "Switch harpoon list", silent = true })
A complete setup using two harpoon lists can be found in ak.config.editor.harpoon
Basic example:
local Harpoonline = require "harpoonline"
Harpoonline.setup({
on_update = function() vim.cmd.redrawstatus() end
})
local HarpoonComponent = {
provider = function() return " " .. Harpoonline.format() .. " " end,
hl = function()
if Harpoonline.is_buffer_harpooned() then
return "MiniHipatternsHack"-- example using mini.hipatterns
end
end,
}
-- A minimal statusline:
require("heirline").setup({ statusline = { HarpoonComponent }})
A proof of concept for AstroNvim v4
{
"rebelot/heirline.nvim",
dependencies = "abeldekat/harpoonline",
config = function(plugin, opts)
local Status = require "astroui.status"
local Harpoonline = require "harpoonline"
Harpoonline.setup {
on_update = function() vim.cmd.redrawstatus() end,
}
local HarpoonComponent = Status.component.builder {
{
provider = function()
local line = Harpoonline.format()
return Status.utils.stylize(line, { padding = { left = 1, right = 1 }})
end,
hl = function()
if Harpoonline.is_buffer_harpooned() then
return { bg = "command", fg = "bg" }
end
end,
},
}
table.insert(opts.statusline, 4, HarpoonComponent) -- after file_info component
require "astronvim.plugins.configs.heirline"(plugin, opts)
end,
}
A proof of concept for NvChad v2.5
---@type ChadrcConfig
local M = {} -- nvchad starter: lua.chadrc.lua
-- Add to config.plugins:
-- {
-- "nvchad/ui",
-- dependencies = {
-- "abeldekat/harpoonline",
-- config = function()
-- require("harpoonline").setup {
-- on_update = function() vim.cmd.redrawstatus() end,
-- }
-- end,
-- },
-- }
M.ui = {
theme = "flexoki-light",
statusline = {
theme = "vscode",
separator_style = "default",
-- Copy local "orders.vscode" from nvchad.stl.utils(plugin nvchad/ui)
-- Add string "harpoon" before "file"
order = { "mode", "harpoon", "file", "diagnostics", "git",
"%=", "lsp_msg", "%=", "lsp", "cursor", "cwd" },
modules = {
-- Add a custom harpoon module, using the file background.
harpoon = function()
return "%#St_file_bg# " .. require("harpoonline").format() .. " "
end,
},
},
}
return M
- Dedicated to lualine
- A single, customizable formatting algorithm
- No caching
- No support for other lists than the default
- @theprimeagen: Harpoon is the most important part of my workflow.
- @echasnovski: The structure of this plugin is heavily based on mini.nvim