abeldekat/harpoonline

github github
marks
stars 27
issues 0
subscribers 1
forks 0
CREATED

2024-03-16

UPDATED

16 days ago


Harpoonline

Create up-to-date harpoon2 information for any place where that information can be useful. For example, in statuslines and the tabline.

TOC

Demo

https://github.com/abeldekat/harpoonline/assets/58370433/ec56eeb2-3cbf-46fe-bc9d-633f6aa8bb9b

Demo of the features. Using lualine and mini.statusline.

1710845846 Heirline in AstroNvim v4

1710925071 Custom statusline in NvChad v2.5

Features

  • 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

Requirements

Setup

Important: don't forget to call require('harpoonline').setup() to enable the plugin. Without that call, the formatter will return an empty string.

Using lazy.nvim and lualine

{
    "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,
}

Using mini.deps and mini.statusline

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() ---->  produce the info
  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

Configuration

The following configuration is implied when calling setup without arguments:

---@class HarpoonLineConfig
Harpoonline.config = {
  -- other nice icons: "󰀱", "", "󱡅"
  ---@type string
  icon = '󰀱', -- An empty string disables showing the icon

  -- Harpoon:list(), without a name, retrieves the default list:
  -- default_list_name: Configures the display name for the default list.
  ---@type string
  default_list_name = '',

  ---@type "extended" | "short"
  formatter = 'extended', -- use a built-in formatter

  formatter_opts = {
    extended = {
      -- An indicator corresponds to a position in the harpoon list
      -- Suggestion: Add an indicator for each configured "select" keybinding
      indicators = { ' 1 ', ' 2 ', ' 3 ', ' 4 ' },
      active_indicators = { '[1]', '[2]', '[3]', '[4]' },

      -- 1 More indicators than items in the harpoon list:
      empty_slot = '', -- ' · ', -- middledot. Disable using empty string

      -- 2 Less indicators than items in the harpoon list
      more_marks_indicator = ' … ', -- horizontal elipsis. Disable using empty string
      more_marks_active_indicator = '[…]', -- Disable using empty string
    },
    short = {
      inner_separator = '|',
    },
  },

  ---@type fun():string|nil
  custom_formatter = nil, -- use this formatter when configured

  ---@type fun()|nil
  on_update = nil, -- optional action to perform after update
}

Note: The icon does not display properly in the browser...

Formatters

Scenario's:

  • A: 3 marks, the current buffer is not harpooned
  • B: 3 marks, the current buffer is harpooned on mark 2

The "extended" built-in

This is the default formatter. Default options: config.formatter_opts.extended

Output A: :anchor: 1 2 3

Output B: :anchor: 1 [2] 3

Note: Five marks, the fifth mark is the active buffer:

Output B: 󰛢 1 2 3 4 […]

The "short" built-in

Add to the config: formatter = 'short'. Default options: config.formatter_opts.short

Output A: :anchor: [3]

Output B: :anchor: [2|3]

Customize a built-in

Harpoonline.setup({
  -- formatter = "extended", -- configure the default formatter
  formatter_opts = {
    extended = { -- remove all spaces...
      indicators = { "1", "2", "3", "4" },
      empty_slot = "·",
      more_marks_indicator = "…", -- horizontal elipsis. Disable with empty string
      more_marks_active_indicator = "[…]", -- Disable with empty string
    },
  },
  on_update = on_update,
})

Output A: :anchor: 123·

Output B: :anchor: 1[2]3·

Use a custom formatter

The following data is kept up-to-date internally, to be processed by formatters:

---@class HarpoonLineData
H.data = {
  -- Harpoon's default list is in use when list_name = nil
  --- @type string|nil
  list_name = nil, -- the name of the current list
  --- @type number
  list_length = 0, -- the length of the current list
  --- @type number|nil
  buffer_idx = nil, -- the mark of the current buffer if harpooned
}

Example:

local Harpoonline = require("harpoonline")
Harpoonline.setup({
  custom_formatter = Harpoonline.gen_formatter(
    ---@param data HarpoonLineData
    ---@return string
    function(data)
      return string.format( -- very short, without the length of the harpoon list
        "%s%s%s",
        "➡️ ",
        data.list_name and string.format("%s ", data.list_name) or "",
        data.buffer_idx and string.format("%d", data.buffer_idx) or "-"
      )
    end
  ),
})

Output A: :arrow_right: -

Output B: :arrow_right: 2

Note: You can also use inner highlights in the formatter function. See the example recipe for NvChad.

Harpoon lists

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

Recipes

Heirline

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 }})
{
  "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,
}

NvChad statusline

---@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

Related plugins

harpoon-lualine

  • Dedicated to lualine
  • A single, customizable formatting algorithm
  • No caching
  • No support for other lists than the default

Acknowledgements

  • @theprimeagen: Harpoon is the most important part of my workflow.
  • @echasnovski: The structure of this plugin is heavily based on mini.nvim
  • @letieu: The extended formatter is inspired by plugin harpoon-lualine