dotfiles/.config/yazi/plugins/custom-shell.yazi/main.lua

223 lines
6.2 KiB
Lua

local state_option = ya.sync(function(state, attr)
return state[attr]
end)
local function shell_choice(shell_val)
-- input is in lowercase always
local alt_name_map = {
kornshell = "ksh",
powershell = "pwsh",
nushell = "nu",
}
local shell_map = {
bash = { shell_val = "bash", supporter = "-ic", wait_cmd = "read", separator = ";" },
zsh = { shell_val = "zsh", supporter = "-ic", wait_cmd = "read", separator = ";" },
fish = { shell_val = "fish", supporter = "-c", wait_cmd = "read", separator = ";" },
pwsh = { shell_val = "pwsh", supporter = "-Command", wait_cmd = "Read-Host", separator = ";" },
sh = { shell_val = "sh", supporter = "-c", wait_cmd = "read", separator = ";" },
ksh = { shell_val = "ksh", supporter = "-c", wait_cmd = "read", separator = ";" },
csh = { shell_val = "csh", supporter = "-c", wait_cmd = "$<", separator = ";" },
tcsh = { shell_val = "tcsh", supporter = "-c", wait_cmd = "$<", separator = ";" },
dash = { shell_val = "dash", supporter = "-c", wait_cmd = "read", separator = ";" },
nu = { shell_val = "nu", supporter = "-l -i -c", wait_cmd = "input", separator = "| print;" },
}
shell_val = alt_name_map[shell_val] or shell_val
local shell_info = shell_map[shell_val]
if shell_info then
return shell_info.shell_val, shell_info.supporter, shell_info.wait_cmd, shell_info.separator
else
return nil, "-c", "read"
end
end
local function manage_extra_args(job)
-- function for dealing with --option, --option=boolean, nil
local function tobool(arg, default)
if type(arg) == "boolean" then
return arg
elseif type(arg) == "string" then
return arg:lower() == "true"
end
-- Fallback in case of nil
return default
end
local block = tobool(job.args.block, true)
local orphan = tobool(job.args.orphan, false)
local wait = tobool(job.args.wait, false)
local interactive = tobool(job.args.confirm, false)
return block, orphan, wait, interactive
end
local function manage_additional_title_text(block, wait)
local txt = ""
if block or wait then
txt = "(" .. (block and wait and "block and wait" or block and "block" or "") .. ")"
end
return txt
end
local function history_add(history_path, cmd_run)
local history_file = history_path
local history = {}
-- Ensure the history file exists
local file = io.open(history_file, "a+")
if file then
file:close()
end
-- Read existing history
for line in io.lines(history_file) do
-- add line if it is not empty
if line:match("%S") then
table.insert(history, line)
end
end
if cmd_run == nil then
return history
end
-- Append the new command to the history if it doesn't already exist
local command_exists = false
for _, cmd in ipairs(history) do
if cmd == cmd_run then
command_exists = true
break
end
end
if not command_exists then
file = io.open(history_file, "a")
if file then
file:write(cmd_run .. "\n")
file:close()
end
end
return history
end
local function history_prev(history_path)
-- get the history commands
local history_cmds = history_add(history_path)
if #history_cmds < 1 then
ya.notify({ title = "Custom-Shell", content = "History is Empty.", timeout = 3 })
return
end
-- preview the commands list in fzf and return selected cmd
for i, cmd in ipairs(history_cmds) do
history_cmds[i] = cmd:gsub("'", "'\\''") -- Escape single quotes
end
local permit = ya.hide()
local cmd = string.format('%s < "%s"', "fzf", history_path)
local handle = io.popen(cmd, "r")
local his_cmd = ""
if handle then
-- strip
his_cmd = string.gsub(handle:read("*all") or "", "^%s*(.-)%s*$", "%1")
handle:close()
end
permit:drop()
return his_cmd
end
local function entry(_, job)
local shell_env = os.getenv("SHELL"):match(".*/(.*)")
local shell_value, cmd, custom_shell_cmd = "", "", ""
local history_path, save_history = state_option("history_path"), state_option("save_history")
if job.args[1] == "auto" or job.args[1] == "history" then
shell_value = shell_env:lower()
elseif job.args[1] == "custom" then
shell_value = job.args[2]
cmd = job.args[3]
-- when the first param is a shell name
elseif job.args[1] ~= "history" then
shell_value = job.args[1]:lower()
end
local shell_val, supp, wait_cmd, separator = shell_choice(shell_value:lower())
if job.args[1] == "history" then
local his_cmd = history_prev(history_path)
if his_cmd == nil then
return
end
local value, event = ya.input({
title = "Custom-Shell History",
position = { "top-center", y = 3, w = 40 },
value = his_cmd,
})
if event == 1 then
-- this may lead to nested zsh -c "zsh -c 'zsh -c ...'"
-- But that's not a problem
cmd = value
end
end
if shell_val == nil then
ya.notify("Unsupported shell: " .. shell_value .. "Choosing Default Shell: " .. shell_env)
shell_val, supp = shell_choice(shell_env)
end
local block, orphan, wait, interactive = manage_extra_args(job) -- , confirm
local additional_title_text = manage_additional_title_text(block, wait)
local input_title = shell_value .. " Shell " .. additional_title_text .. ": "
local event = 1
if job.args[1] ~= "custom" and job.args[1] ~= "history" then
cmd, event = ya.input({
title = input_title,
position = { "top-center", y = 3, w = 40 },
})
end
if event == 1 then
local after_cmd = separator .. (wait and wait_cmd or "exit")
-- for history also, this will be added.
custom_shell_cmd = shell_val .. " " .. supp .. " " .. ya.quote(cmd .. after_cmd, true)
ya.manager_emit("shell", {
custom_shell_cmd,
block = block,
interactive = interactive,
orphan = orphan,
})
if save_history then
if job.args[1] == "history" then
-- to avoid nested "zsh -c 'zsh -c ...'"
history_add(history_path, cmd)
else
history_add(history_path, custom_shell_cmd)
end
end
end
end
--- @since 25.2.7
return {
setup = function(state, options)
local default_history_path = (
ya.target_family() == "windows"
and os.getenv("APPDATA") .. "\\yazi\\config\\plugins\\custom-shell.yazi\\yazi_cmd_history"
) or (os.getenv("HOME") .. "/.config/yazi/plugins/custom-shell.yazi/yazi_cmd_history")
state.history_path = options.history_path or default_history_path
if state.history_path:lower() == "default" then
state.history_path = default_history_path
end
state.save_history = options.save_history and true
end,
entry = entry,
}