automate/020_yazi-plugin_httm-bowie_...

328 lines
10 KiB
Bash

#!/usr/bin/env bash
: <<!
-------------------------------------------------------------------------------
Yazi httm-bowie previewer + toggle — pure Lua (preview pane, toggleable modes)
Cycles through: none (default preview) → last diff → all diffs
Requires: httm, diff, tree (for dirs), realpath
-------------------------------------------------------------------------------
!
set -euo pipefail
DEST=${1:-/etc/skel/}
YAZI_HOME="${DEST}/.config/yazi"
YAZI_PLUGIN_HOME="${YAZI_HOME}/plugins"
BOWIE_PLUGIN_HOME="${YAZI_PLUGIN_HOME}/httm-bowie.yazi"
mkdir -p "${BOWIE_PLUGIN_HOME}"
#----------------------------------------------------------------
conf_print_bowie_previewer() {
cat <<'EOF'
-- httm-bowie.yazi/init.lua
-- Custom previewer: shows bowie diff based on global mode (none/last/all)
local M = {}
-- Shared state via ya.sync (initial: "none")
local mode_get = ya.sync(function() return _G.BOWIE_MODE or "none" end)
function M:peek(job)
local mode = mode_get()
if mode == "none" then
return -- skip to default previewer
end
local path = tostring(job.url)
local is_file = os.execute("test -f " .. ya.quote(path)) == 0 or os.execute("test -h " .. ya.quote(path)) == 0
local real_cmd = Command("realpath"):arg(path):stdout(Command.PIPED):spawn()
if real_cmd then
real_cmd:wait()
path = real_cmd:read_line() or path
path = path:gsub("\n$", "")
end
local content = ""
local function add_header(txt) content = content .. txt .. "\n__\n\n" end
local function add_diff(prev, curr, is_dir)
if is_dir then
local tree_prev = Command("tree"):arg("-RDsa"):arg(prev):stdout(Command.PIPED):spawn()
local tree_curr = Command("tree"):arg("-RDsa"):arg(curr):stdout(Command.PIPED):spawn()
if not tree_prev or not tree_curr then return end
tree_prev:wait()
tree_curr:wait()
local p_out = tree_prev:read_all() or ""
local c_out = tree_curr:read_all() or ""
p_out = p_out:gsub("^.*\n", "", 1):gsub("\n$", "") -- skip first line
c_out = c_out:gsub("^.*\n", "", 1):gsub("\n$", "")
local diff_cmd = Command("diff"):arg("--color=always"):arg("-T"):arg("--label=" .. prev):arg("--label=" .. curr):stdout(Command.PIPED)
diff_cmd:stdin(Command.PIPED):write(p_out .. "\n" .. c_out):spawn() -- hacky, but diff from strings
diff_cmd:wait()
content = content .. (diff_cmd:read_all() or "") .. "\n"
else
local diff = Command("diff"):arg("--color=always"):arg("-T"):arg(prev):arg(curr):stdout(Command.PIPED):spawn()
if diff then
diff:wait()
content = content .. (diff:read_all() or "No diff") .. "\n"
end
end
end
local function get_last_snap(p)
local cmd = Command("httm"):arg("-n"):arg("--dedup-by=contents"):arg("--omit-ditto"):arg("--last-snap"):arg(p):stdout(Command.PIPED):spawn()
if not cmd then return nil end
cmd:wait()
return cmd:read_line():gsub("\n$", "")
end
local function get_all_snaps(p)
local dedup = is_file and "contents" or "disable"
local cmd = Command("httm"):arg("-n"):arg("--dedup-by=" .. dedup):arg("--omit-ditto"):arg(p):stdout(Command.PIPED):spawn()
if not cmd then return {} end
cmd:wait()
local snaps = {}
for line in cmd:read_lines() do
line = line:gsub("\n$", "")
if line ~= "" then table.insert(snaps, line) end
end
return snaps
end
if mode == "last" then
local snap = get_last_snap(path)
if not snap or snap == "" or snap == path then
content = "No previous snapshot or identical to live."
else
add_header(path .. " (last snapshot diff)")
add_diff(snap, path, not is_file)
end
elseif mode == "all" then
local snaps = get_all_snaps(path)
if #snaps == 0 then
content = "No snapshots found."
else
add_header(path .. " (all changes)")
local prev = snaps[1]
for i = 2, #snaps do
local curr = snaps[i]
content = content .. "-- " .. prev .. " → " .. curr .. " --\n"
add_diff(prev, curr, not is_file)
prev = curr
end
content = content .. "-- " .. prev .. " → LIVE (" .. path .. ") --\n"
add_diff(prev, path, not is_file)
end
end
if content == "" then content = "No content generated." end
ya.preview_code({
area = job.area,
hup = false, -- don't update on hover change
content = content,
filetype = "diff", -- for syntax highlight
})
end
function M:seek(job)
self:peek(job) -- simple, no complex seeking
end
return M
EOF
}
conf_print_bowie_previewer | tee "${BOWIE_PLUGIN_HOME}/init.lua"
#----------------------------------------------------------------
# FIXME: Seems to be a separate plugin just to handle toggle/cycle behaiour:
conf_print_bowie_toggle() {
cat <<'EOF'
-- httm-bowie-toggle.yazi/init.lua
-- Toggle mode: none → last → all → none
return {
entry = function()
local modes = { "none", "last", "all" }
local current = _G.BOWIE_MODE or "none"
local next_idx = 1
for i, m in ipairs(modes) do
if m == current then
next_idx = (i % #modes) + 1
break
end
end
_G.BOWIE_MODE = modes[next_idx]
ya.notify({ title = "Bowie", content = "Mode: " .. _G.BOWIE_MODE, timeout = 2 })
-- Force preview refresh
ya.manager_emit("peek", { cx.active.preview.skip or 0, only_if = tostring(cx.active.current.hovered.url), force = true })
end,
}
EOF
}
# conf_print_bowie_toggle | tee "${HTTM_TOGGLE_HOME}/init.lua"
#----------------------------------------------------------------
conf_print_bowie_config() {
cat <<'EOF'
# Add to ~/.config/yazi/yazi.toml
[plugin]
prepend_previewers = [
{ name = "*", run = "httm-bowie", sync = true },
]
EOF
}
#----------------------------------------------------------------
conf_print_bowie_keymap() {
cat <<'EOF'
# Add to ~/.config/yazi/keymap.toml
[[manager.prepend_keymap]]
on = [ "g", "d" ]
run = "plugin httm-bowie-toggle"
desc = "Toggle Bowie preview mode (none/last/all)"
EOF
}
#----------------------------------------------------------------
conf_print_bowie_license() {
cat <<EOF
MIT License
Copyright (c) 2026 ${AUTHOR_NAME}
Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
in the Software without restriction, including without limitation the rights
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
copies of the Software, and to permit persons to whom the Software is
furnished to do so, subject to the following conditions:
The above copyright notice and this permission notice shall be included in all
copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
SOFTWARE.
EOF
}
conf_print_bowie_license | tee "${BOWIE_PLUGIN_HOME}/LICENSE"
#----------------------------------------------------------------
conf_print_bowie_README() {
cat <<'EOF'
This is a specialized plugin for Yazi that integrates `httm` (the Interactive ZFS Snapshot Explorer) directly into your preview pane. It allows you to cycle through different "time-travel" views of your files and directories without leaving the file manager.
Below is a formatted `README.md` you can use for this project.
---
# httm-bowie.yazi
A high-performance **Yazi** plugin that brings `httm` snapshot diffs directly into your preview pane. Cycle through historical versions of files and directories with a single keybind.
## 🌟 Features
* **Three Preview Modes**:
* `none`: Standard Yazi preview (syntax highlighting, images, etc.).
* `last`: Shows a `diff` between the live file and its most recent snapshot.
* `all`: Shows a sequential `diff` chain of all historical changes found by `httm`.
* **Directory Support**: Uses `tree` to diff directory structures if a folder is hovered.
* **Smart Dedup**: Automatically handles `httm`'s deduplication to show only meaningful changes.
* **Native Feel**: Integrated with Yazi's `ya.preview_code` for smooth scrolling and `diff` syntax highlighting.
## 📋 Requirements
Ensure the following are installed and available in your `$PATH`:
* [httm](https://github.com/kimono-koans/httm)
* `diff` (usually pre-installed on Unix)
* `tree` (required for directory previews)
* `realpath`
## 🚀 Installation
### 1. Create the Plugin Folders
Yazi expects plugins in specific directories. Create them and place the `init.lua` files accordingly:
```bash
# Create directories
mkdir -p ~/.config/yazi/plugins/httm-bowie.yazi
mkdir -p ~/.config/yazi/plugins/httm-bowie-toggle.yazi
```
1. Save the **Previewer** code to `~/.config/yazi/plugins/httm-bowie.yazi/init.lua`.
2. Save the **Toggle** code to `~/.config/yazi/plugins/httm-bowie-toggle.yazi/init.lua`.
### 2. Configure `yazi.toml`
Add the previewer to the top of your `prepend_previewers` list so it can intercept the preview before the default handlers:
```toml
[plugin]
prepend_previewers = [
{ name = "*", run = "httm-bowie", sync = true },
]
```
### 3. Configure `keymap.toml`
Bind the toggle function to a key of your choice (e.g., `gd` for "Go Diff"):
```toml
[[manager.prepend_keymap]]
on = [ "g", "d" ]
run = "plugin httm-bowie-toggle"
desc = "Toggle Bowie preview mode (none/last/all)"
```
## 🛠️ Usage
1. **Navigate** to any file or directory inside a ZFS dataset (or any location monitored by `httm`).
2. **Press `gd**` to cycle through modes:
* **Notification: "Mode: last"** → The preview pane now shows the changes made since the last snapshot.
* **Notification: "Mode: all"** → The preview pane shows a scrollable history of all changes across all snapshots.
* **Notification: "Mode: none"** → Returns to standard Yazi behavior.
3. **Scroll** through the diffs using your standard Yazi preview scroll keys.
EOF
}
conf_print_bowie_README | tee "${BOWIE_PLUGIN_HOME}/README.md"
#----------------------------------------------------------------
cat <<EOF
httm-bowie previewer + toggle installed.
Previewer: ${BOWIE_PLUGIN_HOME}/init.lua
Toggle: ${BOWIE_TOGGLE_HOME}/init.lua
conf_print_bowie_config
conf_print_bowie_keymap
Add the above to yazi.toml and keymap.toml.
Restart yazi. Use 'gd' to cycle modes. When 'none', falls back to default preview.
EOF