2024-11-08 12:54:30 +03:00

356 lines
8.8 KiB
Lua

local path_sep = package.config:sub(1, 1)
local get_hovered_path = ya.sync(function(state)
local h = cx.active.current.hovered
if h then
local path = tostring(h.url)
if h.cha.is_dir then
return path .. path_sep
end
return path
else
return ''
end
end)
local get_state_attr = ya.sync(function(state, attr)
return state[attr]
end)
local set_state_attr = ya.sync(function(state, attr, value)
state[attr] = value
end)
local set_bookmarks = ya.sync(function(state, path, value)
state.bookmarks[path] = value
end)
local sort_bookmarks = function(bookmarks, key1, key2, reverse)
reverse = reverse or false
table.sort(bookmarks, function(x, y)
if x[key1] == nil and y[key1] == nil then
return x[key2] < y[key2]
elseif x[key1] == nil then
return false
elseif y[key1] == nil then
return true
else
return x[key1] < y[key1]
end
end)
if reverse then
local n = #bookmarks
for i = 1, math.floor(n / 2) do
bookmarks[i], bookmarks[n - i + 1] = bookmarks[n - i + 1], bookmarks[i]
end
end
return bookmarks
end
local save_to_file = function(mb_path, bookmarks)
local file = io.open(mb_path, "w")
if file == nil then
return
end
local array = {}
for _, item in pairs(bookmarks) do
table.insert(array, item)
end
sort_bookmarks(array, "tag", "key", true)
for _, item in ipairs(array) do
file:write(string.format("%s\t%s\t%s\n", item.tag, item.path, item.key))
end
file:close()
end
local fzf_find = function(cli, mb_path)
local permit = ya.hide()
local cmd = string.format("%s < \"%s\"", cli, mb_path)
local handle = io.popen(cmd, "r")
local result = ""
if handle then
-- strip
result = string.gsub(handle:read("*all") or "", "^%s*(.-)%s*$", "%1")
handle:close()
end
permit:drop()
local tag, path, key = string.match(result or "", "(.-)\t(.-)\t(.*)")
return path
end
local which_find = function(bookmarks)
local cands = {}
for path, item in pairs(bookmarks) do
if #item.tag ~= 0 then
table.insert(cands, { desc = item.tag, on = item.key, path = item.path })
end
end
sort_bookmarks(cands, "on", "desc", false)
if #cands == 0 then
ya.notify {
title = "Bookmarks",
content = "Empty bookmarks",
timeout = 2,
level = "info",
}
return nil
end
local idx = ya.which { cands = cands }
if idx == nil then
return nil
end
return cands[idx].path
end
local action_jump = function(bookmarks, path, jump_notify)
if path == nil then
return
end
local tag = bookmarks[path].tag
if string.sub(path, -1) == path_sep then
ya.manager_emit("cd", { path })
else
ya.manager_emit("reveal", { path })
end
if jump_notify then
ya.notify {
title = "Bookmarks",
content = 'Jump to "' .. tag .. '"',
timeout = 2,
level = "info",
}
end
end
local generate_key = function(bookmarks)
local keys = get_state_attr("keys")
local key2rank = get_state_attr("key2rank")
local mb = {}
for _, item in pairs(bookmarks) do
if #item.key == 1 then
table.insert(mb, item.key)
end
end
if #mb == 0 then
return keys[1]
end
table.sort(mb, function(a, b)
return key2rank[a] < key2rank[b]
end)
local idx = 1
for _, key in ipairs(keys) do
if key2rank[key] < key2rank[mb[idx]] then
return key
end
idx = idx + 1
end
return nil
end
local action_save = function(mb_path, bookmarks, path)
if path == nil or #path == 0 then
return
end
local path_obj = bookmarks[path]
-- check tag
local tag = path_obj and path_obj.tag or path:match(".*[\\/]([^\\/]+)[\\/]?$")
while true do
local value, event = ya.input({
title = "Tag (alias name)",
value = tag,
position = { "top-center", y = 3, w = 40 },
})
if event ~= 1 then
return
end
tag = value or ''
if #tag == 0 then
ya.notify {
title = "Bookmarks",
content = "Empty tag",
timeout = 2,
level = "info",
}
else
-- check the tag
local tag_obj = nil
for _, item in pairs(bookmarks) do
if item.tag == tag then
tag_obj = item
break
end
end
if tag_obj == nil or tag_obj.path == path then
break
end
ya.notify {
title = "Bookmarks",
content = "Duplicated tag",
timeout = 2,
level = "info",
}
end
end
-- check key
local key = path_obj and path_obj.key or generate_key(bookmarks)
while true do
local value, event = ya.input({
title = "Key (1 character, optional)",
value = key,
position = { "top-center", y = 3, w = 40 },
})
if event ~= 1 then
return
end
key = value or ""
if key == "" then
key = ""
break
elseif #key == 1 then
-- check the key
local key_obj = nil
for _, item in pairs(bookmarks) do
if item.key == key then
key_obj = item
break
end
end
if key_obj == nil or key_obj.path == path then
break
else
ya.notify {
title = "Bookmarks",
content = "Duplicated key",
timeout = 2,
level = "info",
}
end
else
ya.notify {
title = "Bookmarks",
content = "The length of key shoule be 1",
timeout = 2,
level = "info",
}
end
end
-- save
set_bookmarks(path, { tag = tag, path = path, key = key })
bookmarks = get_state_attr("bookmarks")
save_to_file(mb_path, bookmarks)
ya.notify {
title = "Bookmarks",
content = '"' .. tag .. '" saved"',
timeout = 2,
level = "info",
}
end
local action_delete = function(mb_path, bookmarks, path)
if path == nil then
return
end
local tag = bookmarks[path].tag
set_bookmarks(path, nil)
bookmarks = get_state_attr("bookmarks")
save_to_file(mb_path, bookmarks)
ya.notify {
title = "Bookmarks",
content = '"' .. tag .. '" deleted',
timeout = 2,
level = "info",
}
end
local action_delete_all = function(mb_path)
local value, event = ya.input({
title = "Delete all bookmarks? (y/n)",
position = { "top-center", y = 3, w = 40 },
})
if event ~= 1 then
return
end
if string.lower(value) == "y" then
set_state_attr("bookmarks", {})
save_to_file(mb_path, {})
ya.notify {
title = "Bookmarks",
content = "All bookmarks deleted",
timeout = 2,
level = "info",
}
else
ya.notify {
title = "Bookmarks",
content = "Cancel delete",
timeout = 2,
level = "info",
}
end
end
return {
setup = function(state, options)
state.path = options.path or
(ya.target_family() == "windows" and os.getenv("APPDATA") .. "\\yazi\\config\\bookmark") or
(os.getenv("HOME") .. "/.config/yazi/bookmark")
state.cli = options.cli or "fzf"
state.jump_notify = options.jump_notify and true
-- init the keys
local keys = options.keys or "0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ"
state.keys = {}
state.key2rank = {}
for i = 1, #keys do
local char = keys:sub(i, i)
table.insert(state.keys, char)
state.key2rank[char] = i
end
-- init the bookmarks
local bookmarks = {}
for _, item in pairs(options.bookmarks or {}) do
bookmarks[item.path] = { tag = item.tag, path = item.path, key = item.key }
end
-- load the config
local file = io.open(state.path, "r")
if file ~= nil then
for line in file:lines() do
local tag, path, key = string.match(line, "(.-)\t(.-)\t(.*)")
if tag and path then
key = key or ""
bookmarks[path] = { tag = tag, path = path, key = key }
end
end
file:close()
end
-- create bookmarks file to enable fzf
save_to_file(state.path, bookmarks)
state.bookmarks = bookmarks
end,
entry = function(self, args)
local action = args[1]
if not action then
return
end
local mb_path, cli, bookmarks, jump_notify = get_state_attr("path"), get_state_attr("cli"), get_state_attr("bookmarks"), get_state_attr("jump_notify")
if action == "save" then
action_save(mb_path, bookmarks, get_hovered_path())
elseif action == "delete_by_key" then
action_delete(mb_path, bookmarks, which_find(bookmarks))
elseif action == "delete_by_fzf" then
action_delete(mb_path, bookmarks, fzf_find(cli, mb_path))
elseif action == "delete_all" then
action_delete_all(mb_path)
elseif action == "jump_by_key" then
action_jump(bookmarks, which_find(bookmarks), jump_notify)
elseif action == "jump_by_fzf" then
action_jump(bookmarks, fzf_find(cli, mb_path), jump_notify)
elseif action == "rename_by_key" then
action_save(mb_path, bookmarks, which_find(bookmarks))
elseif action == "rename_by_fzf" then
action_save(mb_path, bookmarks, fzf_find(cli, mb_path))
end
end,
}