mirror of
https://github.com/ThePrimeagen/harpoon.git
synced 2025-07-13 17:40:25 +00:00
286 lines
9.5 KiB
Lua
286 lines
9.5 KiB
Lua
local Extensions = require("harpoon.extensions")
|
|
local Logger = require("harpoon.logger")
|
|
local Path = require("plenary.path")
|
|
local function normalize_path(buf_name, root)
|
|
return Path:new(buf_name):make_relative(root)
|
|
end
|
|
|
|
local M = {}
|
|
local DEFAULT_LIST = "__harpoon_files"
|
|
M.DEFAULT_LIST = DEFAULT_LIST
|
|
|
|
---@alias HarpoonListItem {value: any, context: any}
|
|
---@alias HarpoonListFileItem {value: string, context: {row: number, col: number}}
|
|
---@alias HarpoonListFileOptions {split: boolean, vsplit: boolean, tabedit: boolean}
|
|
|
|
---@class HarpoonPartialConfigItem
|
|
---@field select_with_nil? boolean defaults to false
|
|
---@field encode? (fun(list_item: HarpoonListItem): string) | boolean
|
|
---@field decode? (fun(obj: string): any)
|
|
---@field display? (fun(list_item: HarpoonListItem, config: HarpoonPartialConfigItem): string)
|
|
---@field select? (fun(list_item?: HarpoonListItem, list: HarpoonList, options: any?): nil)
|
|
---@field equals? (fun(list_line_a: HarpoonListItem, list_line_b: HarpoonListItem): boolean)
|
|
---@field create_list_item? fun(config: HarpoonPartialConfigItem, item: any?): HarpoonListItem
|
|
---@field BufLeave? fun(evt: any, list: HarpoonList): nil
|
|
---@field VimLeavePre? fun(evt: any, list: HarpoonList): nil
|
|
---@field get_root_dir? fun(): string
|
|
|
|
---@class HarpoonSettings
|
|
---@field save_on_toggle boolean defaults to false
|
|
---@field sync_on_ui_close? boolean
|
|
---@field key (fun(): string)
|
|
|
|
---@class HarpoonPartialSettings
|
|
---@field save_on_toggle? boolean
|
|
---@field sync_on_ui_close? boolean
|
|
---@field key? (fun(): string)
|
|
|
|
---@class HarpoonConfig
|
|
---@field default HarpoonPartialConfigItem
|
|
---@field settings HarpoonSettings
|
|
---@field [string] HarpoonPartialConfigItem
|
|
|
|
---@class HarpoonPartialConfig
|
|
---@field default? HarpoonPartialConfigItem
|
|
---@field settings? HarpoonPartialSettings
|
|
---@field [string] HarpoonPartialConfigItem
|
|
|
|
---@return HarpoonPartialConfigItem
|
|
function M.get_config(config, name)
|
|
return vim.tbl_extend("force", {}, config.default, config[name] or {})
|
|
end
|
|
|
|
---@return HarpoonConfig
|
|
function M.get_default_config()
|
|
return {
|
|
|
|
settings = {
|
|
save_on_toggle = false,
|
|
sync_on_ui_close = false,
|
|
|
|
key = function()
|
|
return vim.loop.cwd()
|
|
end,
|
|
},
|
|
|
|
default = {
|
|
|
|
--- select_with_nill allows for a list to call select even if the provided item is nil
|
|
select_with_nil = false,
|
|
|
|
---@param obj HarpoonListItem
|
|
---@return string
|
|
encode = function(obj)
|
|
return vim.json.encode(obj)
|
|
end,
|
|
|
|
---@param str string
|
|
---@return HarpoonListItem
|
|
decode = function(str)
|
|
local decodedItem = vim.json.decode(str)
|
|
|
|
-- NOTE: prevents this absolute path sometimes bug by forcing
|
|
-- everything to be absolute path always
|
|
decodedItem.value = vim.loop.fs_realpath(decodedItem.value)
|
|
return decodedItem
|
|
end,
|
|
|
|
---@param list_item HarpoonListItem
|
|
---@param config HarpoonPartialConfigItem
|
|
display = function(list_item, config)
|
|
return normalize_path(list_item.value, config.get_root_dir())
|
|
end,
|
|
|
|
--- the select function is called when a user selects an item from
|
|
--- the corresponding list and can be nil if select_with_nil is true
|
|
---@param list_item? HarpoonListFileItem
|
|
---@param list HarpoonList
|
|
---@param options HarpoonListFileOptions
|
|
select = function(list_item, list, options)
|
|
Logger:log(
|
|
"config_default#select",
|
|
list_item,
|
|
list.name,
|
|
options
|
|
)
|
|
if list_item == nil then
|
|
return
|
|
end
|
|
|
|
options = options or {}
|
|
|
|
local bufnr = vim.fn.bufnr(list_item.value)
|
|
local set_position = false
|
|
if bufnr == -1 then -- must create a buffer!
|
|
set_position = true
|
|
-- bufnr = vim.fn.bufnr(list_item.value, true)
|
|
bufnr = vim.fn.bufadd(list_item.value)
|
|
end
|
|
if not vim.api.nvim_buf_is_loaded(bufnr) then
|
|
vim.fn.bufload(bufnr)
|
|
vim.api.nvim_set_option_value("buflisted", true, {
|
|
buf = bufnr,
|
|
})
|
|
end
|
|
|
|
if options.vsplit then
|
|
vim.cmd("vsplit")
|
|
elseif options.split then
|
|
vim.cmd("split")
|
|
elseif options.tabedit then
|
|
vim.cmd("tabedit")
|
|
end
|
|
|
|
vim.api.nvim_set_current_buf(bufnr)
|
|
|
|
if set_position then
|
|
local lines = vim.api.nvim_buf_line_count(bufnr)
|
|
|
|
local edited = false
|
|
if list_item.context.row > lines then
|
|
list_item.context.row = lines
|
|
edited = true
|
|
end
|
|
|
|
local row = list_item.context.row
|
|
local row_text =
|
|
vim.api.nvim_buf_get_lines(0, row - 1, row, false)
|
|
local col = #row_text[1]
|
|
|
|
if list_item.context.col > col then
|
|
list_item.context.col = col
|
|
edited = true
|
|
end
|
|
|
|
vim.api.nvim_win_set_cursor(0, {
|
|
list_item.context.row or 1,
|
|
list_item.context.col or 0,
|
|
})
|
|
|
|
if edited then
|
|
Extensions.extensions:emit(
|
|
Extensions.event_names.POSITION_UPDATED,
|
|
{
|
|
list_item = list_item,
|
|
}
|
|
)
|
|
end
|
|
end
|
|
|
|
Extensions.extensions:emit(Extensions.event_names.NAVIGATE, {
|
|
buffer = bufnr,
|
|
})
|
|
end,
|
|
|
|
---@param list_item_a HarpoonListItem
|
|
---@param list_item_b HarpoonListItem
|
|
equals = function(list_item_a, list_item_b)
|
|
if list_item_a == nil and list_item_b == nil then
|
|
return true
|
|
elseif list_item_a == nil or list_item_b == nil then
|
|
return false
|
|
end
|
|
|
|
return list_item_a.value == list_item_b.value
|
|
end,
|
|
|
|
get_root_dir = function()
|
|
return vim.loop.cwd()
|
|
end,
|
|
|
|
---@param _ HarpoonPartialConfigItem
|
|
---@param name? any
|
|
---@return HarpoonListItem
|
|
create_list_item = function(_, name)
|
|
if name ~= "" then
|
|
if name then
|
|
name = vim.fs.normalize(name)
|
|
else
|
|
name = vim.api.nvim_buf_get_name(
|
|
vim.api.nvim_get_current_buf()
|
|
)
|
|
end
|
|
name = vim.loop.fs_realpath(name)
|
|
end
|
|
|
|
Logger:log("config_default#create_list_item", name)
|
|
|
|
local bufnr = vim.fn.bufnr(name, false)
|
|
|
|
local pos = { 1, 0 }
|
|
if bufnr ~= -1 then
|
|
pos = vim.api.nvim_win_get_cursor(0)
|
|
end
|
|
|
|
return {
|
|
value = name,
|
|
context = {
|
|
row = pos[1],
|
|
col = pos[2],
|
|
},
|
|
}
|
|
end,
|
|
|
|
---@param arg {buf: number}
|
|
---@param list HarpoonList
|
|
BufLeave = function(arg, list)
|
|
local bufnr = arg.buf
|
|
local bufname = vim.api.nvim_buf_get_name(bufnr)
|
|
local item = list:get_by_path(bufname)
|
|
|
|
if item then
|
|
local pos = vim.api.nvim_win_get_cursor(0)
|
|
|
|
Logger:log(
|
|
"config_default#BufLeave updating position",
|
|
bufnr,
|
|
bufname,
|
|
item,
|
|
"to position",
|
|
pos
|
|
)
|
|
|
|
item.context.row = pos[1]
|
|
item.context.col = pos[2]
|
|
|
|
Extensions.extensions:emit(
|
|
Extensions.event_names.POSITION_UPDATED,
|
|
item
|
|
)
|
|
end
|
|
end,
|
|
|
|
autocmds = { "BufLeave" },
|
|
},
|
|
}
|
|
end
|
|
|
|
---@param partial_config HarpoonPartialConfig?
|
|
---@param latest_config HarpoonConfig?
|
|
---@return HarpoonConfig
|
|
function M.merge_config(partial_config, latest_config)
|
|
partial_config = partial_config or {}
|
|
local config = latest_config or M.get_default_config()
|
|
for k, v in pairs(partial_config) do
|
|
if k == "settings" then
|
|
config.settings = vim.tbl_extend("force", config.settings, v)
|
|
elseif k == "default" then
|
|
config.default = vim.tbl_extend("force", config.default, v)
|
|
else
|
|
config[k] = vim.tbl_extend("force", config[k] or {}, v)
|
|
end
|
|
end
|
|
return config
|
|
end
|
|
|
|
---@param settings HarpoonPartialSettings
|
|
function M.create_config(settings)
|
|
local config = M.get_default_config()
|
|
for k, v in ipairs(settings) do
|
|
config.settings[k] = v
|
|
end
|
|
return config
|
|
end
|
|
|
|
return M
|