239 lines
7.2 KiB
Bash
239 lines
7.2 KiB
Bash
#!/usr/bin/env bash
|
|
|
|
: <<!
|
|
# ------------------------------------------------------------------------------
|
|
# Yazi nicotine plugin — pure Lua reimplementation (no external nicotine script)
|
|
# Requires: httm, git, tar, find, stat, realpath (or readlink -f), mktemp
|
|
# ------------------------------------------------------------------------------
|
|
!
|
|
set -euo pipefail
|
|
|
|
DEST=${1:-/etc/skel/}
|
|
|
|
YAZI_HOME="${DEST}/.config/yazi"
|
|
YAZI_PLUGIN_HOME="${YAZI_HOME}/plugins"
|
|
NICOTINE_PLUGIN_HOME="${YAZI_PLUGIN_HOME}/httm-nicotine.yazi"
|
|
|
|
mkdir -p "${NICOTINE_PLUGIN_HOME}"
|
|
|
|
conf_print_nicotine_main() {
|
|
cat <<'EOF'
|
|
local function log(msg)
|
|
-- ya.err("[nicotine] " .. msg) -- uncomment for debug logging to yazi log
|
|
end
|
|
|
|
-- Safe git identity (prevents commit failures when git config is missing)
|
|
local GIT_ENV = {
|
|
GIT_AUTHOR_NAME = "yazi-nicotine",
|
|
GIT_AUTHOR_EMAIL = "yazi-nicotine@localhost",
|
|
GIT_COMMITTER_NAME = "yazi-nicotine",
|
|
GIT_COMMITTER_EMAIL = "yazi-nicotine@localhost",
|
|
}
|
|
|
|
-- Grab selected files / hovered file + cwd (sync context only)
|
|
local get_state = ya.sync(function()
|
|
local targets = {}
|
|
local selected = cx.active.selected
|
|
|
|
if #selected == 0 then
|
|
local hovered = cx.active.current.hovered
|
|
if hovered then
|
|
table.insert(targets, tostring(hovered.url))
|
|
end
|
|
else
|
|
for _, item in pairs(selected) do
|
|
table.insert(targets, tostring(item.url))
|
|
end
|
|
end
|
|
|
|
return {
|
|
targets = targets,
|
|
cwd = tostring(cx.active.current.cwd),
|
|
}
|
|
end)
|
|
|
|
local function run(cmd_builder)
|
|
local child, err = cmd_builder:spawn()
|
|
if not child then
|
|
ya.notify({ title = "Nicotine", content = "Spawn failed: " .. tostring(err), level = "error" })
|
|
return nil
|
|
end
|
|
local status = child:wait()
|
|
if not status.success then
|
|
ya.notify({ title = "Nicotine", content = cmd_builder._program .. " failed (code " .. tostring(status.code) .. ")", level = "error" })
|
|
return nil
|
|
end
|
|
return status
|
|
end
|
|
|
|
local function get_modification_time(path)
|
|
local st = run(Command("stat"):arg("-c"):arg("%Y"):arg(path))
|
|
if not st then return os.time() end -- fallback
|
|
local code = st.output and tonumber(st.output:match("^%d+")) or os.time()
|
|
return code
|
|
end
|
|
|
|
local function copy_add_commit(debug, src_path, repo_dir, commit_msg_suffix)
|
|
local cp = Command("cp"):arg("-a"):arg(src_path):arg(repo_dir .. "/")
|
|
run(cp)
|
|
|
|
local date_unix = get_modification_time(src_path)
|
|
local date_str = os.date("!%Y-%m-%dT%H:%M:%S", date_unix)
|
|
|
|
local git = Command("git"):cwd(repo_dir)
|
|
:env(GIT_ENV.GIT_AUTHOR_NAME, GIT_ENV.GIT_AUTHOR_NAME)
|
|
:env(GIT_ENV.GIT_AUTHOR_EMAIL, GIT_ENV.GIT_AUTHOR_EMAIL)
|
|
:env(GIT_ENV.GIT_COMMITTER_NAME, GIT_ENV.GIT_COMMITTER_NAME)
|
|
:env(GIT_ENV.GIT_COMMITTER_EMAIL,GIT_ENV.GIT_COMMITTER_EMAIL)
|
|
|
|
run(git:arg("add"):arg("--all"))
|
|
|
|
local commit = git:arg("commit")
|
|
:arg("--quiet")
|
|
:arg("--allow-empty")
|
|
:arg("--message"):arg("httm snapshot: " .. commit_msg_suffix)
|
|
:arg("--date"):arg(date_str)
|
|
|
|
if debug then commit = commit:arg("--verbose") end
|
|
|
|
run(commit)
|
|
end
|
|
|
|
-- Recursive collection of unique versions (mimics original traverse + get_unique_versions)
|
|
local function process_path(debug, path, repo_dir)
|
|
local httm_out = ""
|
|
local httm_cmd = Command("httm"):arg("-n"):arg("--omit-ditto"):arg(path)
|
|
:stdout(Command.PIPED):stderr(Command.PIPED)
|
|
|
|
local child, _ = httm_cmd:spawn()
|
|
if child then
|
|
local status = child:wait()
|
|
if status.success then
|
|
httm_out = child:read_lines() or ""
|
|
end
|
|
end
|
|
|
|
local versions = {}
|
|
for line in httm_out:gmatch("[^\n]+") do
|
|
if line ~= "" then table.insert(versions, line) end
|
|
end
|
|
|
|
if #versions <= 1 then
|
|
-- no snapshots or single version → just current file/dir
|
|
copy_add_commit(debug, path, repo_dir, "current version of " .. path)
|
|
else
|
|
for _, ver in ipairs(versions) do
|
|
copy_add_commit(debug, ver, repo_dir, "snapshot: " .. ver)
|
|
end
|
|
end
|
|
|
|
-- If directory, recurse into children
|
|
local is_dir = run(Command("test"):arg("-d"):arg(path))
|
|
if is_dir then
|
|
local find_cmd = Command("find"):arg(path):arg("-mindepth"):arg("1"):arg("-maxdepth"):arg("1")
|
|
:stdout(Command.PIPED)
|
|
local child = find_cmd:spawn()
|
|
if child then
|
|
local status = child:wait()
|
|
if status.success then
|
|
for line in child:read_lines() do
|
|
if line ~= "" then
|
|
local sub = line
|
|
local base = sub:match("^.*/([^/]+)$") or "unknown"
|
|
local sub_repo = repo_dir .. "/" .. base
|
|
run(Command("mkdir"):arg("-p"):arg(sub_repo))
|
|
process_path(debug, sub, sub_repo)
|
|
end
|
|
end
|
|
end
|
|
end
|
|
end
|
|
end
|
|
|
|
return {
|
|
entry = function(_, args)
|
|
local state = get_state()
|
|
local targets = state.targets
|
|
local cwd = state.cwd
|
|
|
|
if #targets == 0 then
|
|
return ya.notify({ title = "Nicotine", content = "No files selected or hovered", level = "warn" })
|
|
end
|
|
|
|
local is_archive = args[1] == "--archive"
|
|
local debug = false -- set to true for verbose git output
|
|
|
|
local tmp_base = os.getenv("TMPDIR") or "/tmp"
|
|
local rand = math.random(10000000, 99999999)
|
|
local tmp_dir = string.format("%s/yazi-nicotine-%d", tmp_base, rand)
|
|
local repo_dir = tmp_dir .. "/nicotine-combined-git"
|
|
|
|
run(Command("mkdir"):arg("-p"):arg(repo_dir))
|
|
|
|
ya.notify({
|
|
title = "Nicotine",
|
|
content = is_archive and "Creating archive..." or "Building git history...",
|
|
timeout = 3,
|
|
})
|
|
|
|
run(Command("git"):cwd(repo_dir):arg("init"):arg("--quiet"))
|
|
|
|
for _, p in ipairs(targets) do
|
|
local real = run(Command("realpath"):arg(p))
|
|
if real then
|
|
process_path(debug, real.output:match("^%S+"), repo_dir)
|
|
end
|
|
end
|
|
|
|
if is_archive then
|
|
local archive_path = cwd .. "/nicotine-combined-git.tar.gz"
|
|
run(Command("tar"):cwd(tmp_dir):arg("-zcf"):arg(archive_path):arg("nicotine-combined-git"))
|
|
run(Command("rm"):arg("-rf"):arg(tmp_dir))
|
|
ya.notify({ title = "Nicotine", content = "Archive created:\n" .. archive_path, timeout = 5 })
|
|
else
|
|
-- Open lazygit
|
|
local permit = ui.hide()
|
|
local lg = Command("lazygit")
|
|
:cwd(repo_dir)
|
|
:stdin(Command.INHERIT)
|
|
:stdout(Command.INHERIT)
|
|
:stderr(Command.INHERIT)
|
|
|
|
local child, err = lg:spawn()
|
|
if not child then
|
|
permit:drop()
|
|
run(Command("rm"):arg("-rf"):arg(tmp_dir))
|
|
return ya.notify({ title = "Nicotine", content = "lazygit failed: " .. tostring(err), level = "error" })
|
|
end
|
|
|
|
child:wait()
|
|
permit:drop()
|
|
|
|
run(Command("rm"):arg("-rf"):arg(tmp_dir))
|
|
ya.notify({ title = "Nicotine", content = "Session ended.", timeout = 2 })
|
|
end
|
|
end,
|
|
}
|
|
EOF
|
|
}
|
|
|
|
conf_print_nicotine_main | tee "${NICOTINE_PLUGIN_HOME}/main.lua"
|
|
|
|
conf_print_nicotine_keymap() {
|
|
cat <<'EOF'
|
|
[[manager.prepend_keymap]]
|
|
on = [ "g", "n" ]
|
|
run = "plugin nicotine"
|
|
desc = "Nicotine: view git history of selection in lazygit"
|
|
|
|
[[manager.prepend_keymap]]
|
|
on = [ "g", "z" ]
|
|
run = "plugin nicotine --args='--archive'"
|
|
desc = "Nicotine: create .tar.gz snapshot archive of selection"
|
|
EOF
|
|
}
|
|
# conf_print_nicotine_keymap | tee -a "${NICOTINE_PLUGIN_HOME}/keymap.toml"
|
|
|
|
echo "Plugin location: ${NICOTINE_PLUGIN_HOME}/main.lua"
|
|
echo "Remember to restart yazi or run :plugin --reload nicotine"
|