328 lines
10 KiB
Bash
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
|