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
14 changes: 14 additions & 0 deletions lua/astrocommunity/recipes/harpoon-bufsync/README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,14 @@
# Harpoon BufSync

> Bidirectional buffer synchronization between AstroNvim tabline and Harpoon marks

## Overview

Harpoon lets you pin frequently-used files for quick navigation. But your tabline (Heirline) doesn't reflect that order β€” it tracks buffers by open-time and internal buffer ID.

This recipe bridges that gap with **two keybindings**:

- **Harpoon β†’ Tabline** (`<Leader>bsh`): Closes non-Harpoon buffers, opens missing Harpoon files, and sorts the tabline to match your Harpoon order
- **Tabline β†’ Harpoon** (`<Leader><Leader>b`): Resets your Harpoon marks to match currently open buffers in tabline order

Work naturally with your buffers, snapshot them into Harpoon, or restore your Harpoon set β€” both directions available on demand.
95 changes: 95 additions & 0 deletions lua/astrocommunity/recipes/harpoon-bufsync/init.lua
Original file line number Diff line number Diff line change
@@ -0,0 +1,95 @@
return {
{
"AstroNvim/astrocore",
---@param opts AstroCoreOpts
opts = function(_, opts)
opts.mappings = opts.mappings or {}
opts.mappings.n = opts.mappings.n or {}

-- ── <Leader><Leader>b: Reset Harpoon from open buffers ──────
opts.mappings.n["<Leader><Leader>b"] = {
function()
local harpoon = require "harpoon"
local list = harpoon:list()
local buffer = require "astrocore.buffer"

local current_buf = vim.api.nvim_get_current_buf()

list.items = {}

-- Re-add each open buffer, preserving tabline order
for _, bufnr in ipairs(vim.t.bufs or {}) do
if buffer.is_valid(bufnr) then
local path = vim.api.nvim_buf_get_name(bufnr)
if path ~= "" and vim.fn.filereadable(path) == 1 then
-- Switch to this buffer so Harpoon's add() picks it up
pcall(vim.api.nvim_set_current_buf, bufnr)
list:add()
end
end
end

pcall(vim.api.nvim_set_current_buf, current_buf)

vim.notify("Harpoon marks reset: " .. #list.items .. " files from tabline", vim.log.levels.INFO)
end,
desc = "Reset Harpoon from buffers",
}

-- ── <Leader>bsh: Sync tabline to Harpoon order ─────────────
opts.mappings.n["<Leader>bsh"] = {
function()
local harpoon = require "harpoon"
local harpoon_items = harpoon:list().items

-- Build path -> harpoon index map
local harpoon_order = {}
for i, item in ipairs(harpoon_items) do
local path = vim.fn.fnamemodify(item.value, ":p")
harpoon_order[path] = i
end

-- Get current tab's buffer list (this IS the tabline order)
local bufs = vim.t.bufs or {}

-- Close buffers NOT in Harpoon list
for _, bufnr in ipairs(bufs) do
if require("astrocore.buffer").is_valid(bufnr) then
local buf_path = vim.fn.fnamemodify(vim.api.nvim_buf_get_name(bufnr), ":p")
if not harpoon_order[buf_path] then require("astrocore.buffer").close(bufnr) end
end
end

-- Open Harpoon files that aren't open yet
local open_paths = {}
for _, bufnr in ipairs(vim.t.bufs or {}) do
open_paths[vim.fn.fnamemodify(vim.api.nvim_buf_get_name(bufnr), ":p")] = true
end

for _, item in ipairs(harpoon_items) do
local path = vim.fn.fnamemodify(item.value, ":p")
if not open_paths[path] and vim.fn.filereadable(path) == 1 then
vim.cmd("edit " .. vim.fn.fnameescape(path))
end
end

-- Re-sort vim.t.bufs by Harpoon order
local new_order = {}
for _, bufnr in ipairs(vim.t.bufs or {}) do
local buf_path = vim.fn.fnamemodify(vim.api.nvim_buf_get_name(bufnr), ":p")
if harpoon_order[buf_path] then
table.insert(new_order, { idx = harpoon_order[buf_path], bufnr = bufnr })
end
end
table.sort(new_order, function(a, b) return a.idx < b.idx end)

-- Write back the sorted order
vim.t.bufs = vim.tbl_map(function(item) return item.bufnr end, new_order)

vim.cmd "redrawtabline"
end,
desc = "Sort by Harpoon",
}
end,
},
}
Loading