lru
This commit is contained in:
@@ -1,109 +1,150 @@
|
||||
zeditor-remote() {
|
||||
ensure() {
|
||||
local error
|
||||
for name in "$@"; do
|
||||
if ! command -v "${name}" >/dev/null; then
|
||||
printf >&2 '%s\n' "ERROR: '$name' not found in PATH"
|
||||
error=1
|
||||
fi
|
||||
done
|
||||
if [[ -n "$error" ]]; then
|
||||
return 1
|
||||
fi
|
||||
}
|
||||
ensure xe fd rg fzf gum || return 1
|
||||
local prefix="${1:-repos}"
|
||||
local statedir="${XDG_STATE_HOME:-"$HOME/.local/state/"}/zeditor-remote-sh"
|
||||
|
||||
local spin=""
|
||||
# doesn't forward stdin
|
||||
if command -v gum >/dev/null; then
|
||||
if printf '%s\n' "$(gum --version)" "gum version 0.15.1" | sort -C -V; then
|
||||
: # older version don't forward stdin
|
||||
else
|
||||
spin="gum spin --show-output --show-error --"
|
||||
fi
|
||||
local -a missing=()
|
||||
command -v xe >/dev/null || missing+=('xe')
|
||||
command -v fd >/dev/null || missing+=('fd')
|
||||
command -v rg >/dev/null || missing+=('rg')
|
||||
command -v fzf >/dev/null || missing+=('fzf')
|
||||
command -v gum >/dev/null || missing+=('gum')
|
||||
if [[ "${#missing[@]}" -gt 0 ]]; then
|
||||
printf >&2 "%s\n" "ERROR: Not found in PATH:$(printf " %s" "${missing[@]}")"
|
||||
return 1
|
||||
fi
|
||||
|
||||
local host=$(
|
||||
{
|
||||
printf '%s\n' localhost
|
||||
{
|
||||
# ask tailscale
|
||||
if command -v tailscale >/dev/null; then
|
||||
tailscale status --json | jq .Peer[].HostName -r;
|
||||
fi
|
||||
# search .ssh/known_hosts, strip DNS search domains to deduplicate
|
||||
if [[ -r /etc/resolv.conf && -r "$HOME"/.ssh/known_hosts ]]; then
|
||||
local -a hosts
|
||||
readarray -d $'\n' -t hosts < <(
|
||||
cut <"$HOME"/.ssh/known_hosts -d' ' -f1 | sort -u \
|
||||
| grep -vE '^\[' \
|
||||
| grep -v '[,@]' \
|
||||
| grep -Ev '^([0-9]{0,3}\.){3}[0-9]{0,3}$' \
|
||||
| grep -Ev '^([0-9a-fA-F]{0,4}:){0,7}:?([0-9a-fA-F]{0,4}:){0,6}[0-9a-fA-F]{0,4}$'
|
||||
)
|
||||
local -a domains
|
||||
readarray -d ' ' -t domains < <(
|
||||
grep </etc/resolv.conf '^search ' | cut -d' ' -f2-
|
||||
)
|
||||
printf '%s\n' "${hosts[@]}" \
|
||||
| sed -E "s/\\.($(IFS='|'; printf "%s" "${domains[*]}"))\$//g" \
|
||||
| grep -v '[.:]'
|
||||
fi
|
||||
} | grep -v '^localhost$' | sort -u
|
||||
} | fzf --multi --reverse --bind 'ctrl-a:toggle-all' --height=25 --cycle \
|
||||
| sed -e 's/%/%%/g'
|
||||
)
|
||||
if [[ -z "$host" ]]; then
|
||||
if [[ ! -v HOME ]]; then
|
||||
printf >&2 "%s\n" "ERROR: $HOME is not set"
|
||||
return 1
|
||||
fi
|
||||
|
||||
# make $prefix relative to $HOME
|
||||
if [[ "$prefix" =~ ^/ ]]; then
|
||||
prefix=${prefix//"$HOME/"/}
|
||||
fi
|
||||
if [[ "$prefix" =~ ^/ ]]; then
|
||||
printf >&2 "%s\n" "ERROR: prefix not in \$HOME ($prefix)"
|
||||
return 1
|
||||
fi
|
||||
|
||||
uniq-stable() (
|
||||
command cat -n | sort -b --key=2.1 -u | sort -n | cut -f2-
|
||||
)
|
||||
|
||||
local hosts=$(
|
||||
{
|
||||
printf '%s\n' localhost
|
||||
{
|
||||
if [[ -s "$statedir"/ssh-host-history ]]; then
|
||||
cat "$statedir"/ssh-host-history
|
||||
fi
|
||||
|
||||
# ask tailscale
|
||||
if command -v tailscale >/dev/null; then
|
||||
tailscale status --json | jq .Peer[].HostName -r
|
||||
fi
|
||||
|
||||
# search .ssh/known_hosts, strip DNS search domains to deduplicate
|
||||
if [[ -r /etc/resolv.conf && -r "$HOME"/.ssh/known_hosts ]]; then
|
||||
local -a hosts
|
||||
readarray -d $'\n' -t hosts < <(
|
||||
cut <"$HOME"/.ssh/known_hosts -d' ' -f1 | sort -u \
|
||||
| grep -vE '^\[' \
|
||||
| grep -v '[,@]' \
|
||||
| grep -Ev '^([0-9]{0,3}\.){3}[0-9]{0,3}$' \
|
||||
| grep -Ev '^([0-9a-fA-F]{0,4}:){0,7}:?([0-9a-fA-F]{0,4}:){0,6}[0-9a-fA-F]{0,4}$'
|
||||
)
|
||||
local -a domains
|
||||
readarray -d ' ' -t domains < <(
|
||||
grep </etc/resolv.conf '^search ' | cut -d' ' -f2-
|
||||
)
|
||||
printf '%s\n' "${hosts[@]}" \
|
||||
| sed -E "s/\\.($(IFS='|'; printf "%s" "${domains[*]}"))\$//g" \
|
||||
| grep -v '[.:]'
|
||||
fi
|
||||
} | grep -v '^localhost$' | uniq-stable
|
||||
} | fzf --sync --multi --reverse --bind 'ctrl-a:toggle-all' --height=25 --cycle \
|
||||
| sed -e 's/%/%%/g'
|
||||
)
|
||||
if [[ -z "$hosts" ]]; then
|
||||
return 1
|
||||
fi
|
||||
|
||||
# local spin=(gum spin --show-output --show-error --)
|
||||
local spin=(gum spin --show-output --)
|
||||
local repos=$(
|
||||
export prefix statedir
|
||||
worker() {
|
||||
local host="$1"
|
||||
# TODO: this assumes remote hosts only code in 'repos/'
|
||||
# TODO: this assumes remote hosts has 'fd', use 'find' instead?
|
||||
fdargs=(--hidden --max-depth 5)
|
||||
# accept all, persist nothing, to scan hosts we've yet to trust,
|
||||
# zed will still complain however
|
||||
# accept all, persist nothing, to scan hosts we've yet to trust, zed will still complain however
|
||||
sshargs=(-o StrictHostKeyChecking=no -o UserKnownHostsFile=/dev/null)
|
||||
if [[ "$host" = localhost ]]; then
|
||||
cd "$HOME"
|
||||
fd "^\.git$" repos/ "${fdargs[@]}" -L -x printf '~/%q\n' "{//}"
|
||||
fd "^\.git$" "$prefix/" "${fdargs[@]}" -L -x printf '~/%q\n' "{//}"
|
||||
else
|
||||
# TODO: report timeouts?
|
||||
# TODO: auto escape this
|
||||
timeout 5 ssh "${sshargs[@]}" 2>/dev/null "$host" \
|
||||
fd "^\.git$" repos/ "${fdargs[@]}" -L -x printf "'ssh://%s/~/%q\n'" "'$host'" "'{//}'"
|
||||
fd "^\.git$" "'$prefix/'" "${fdargs[@]}" -L -x printf "'ssh://%s/~/%q\n'" "'$host'" "'{//}'"
|
||||
fi
|
||||
}
|
||||
$spin xe <<<"$host" -j"$(wc -l <<<"$host")" -s "$(declare -f worker); worker \"\$@\"" 2>/dev/null ||:
|
||||
"${spin[@]}" xe -f <(printf "%s\n" "$hosts") -j"$(wc -l <<<"$hosts")" -s "$(declare -f worker); worker \"\$@\"" ||:
|
||||
)
|
||||
if [[ -z "${repos}" ]]; then
|
||||
printf >&2 '%s\n' "ERROR: no repos found!"
|
||||
return 1
|
||||
fi
|
||||
|
||||
local choice=$(sort <<<"$repos" | fzf --reverse --cycle --height=25)
|
||||
local choice=$(
|
||||
{
|
||||
if [[ -s "$statedir"/ssh-repo-history ]]; then
|
||||
grep <"$statedir"/ssh-repo-history "$(printf "^ssh://%s/\n" "${hosts[@]}")"
|
||||
fi
|
||||
sort <<<"$repos"
|
||||
} | uniq-stable | fzf -sync --reverse --cycle --height=25)
|
||||
if [[ -z "${choice}" ]]; then
|
||||
return
|
||||
fi
|
||||
|
||||
local zed_cmd="zeditor ${choice}"
|
||||
local host=$(
|
||||
rg <<<"$choice" '^ssh://([^/]*)/(~/.*)$' -r '$1' || printf "%s\n" localhost
|
||||
)
|
||||
local ssh_cd_cmd=$(
|
||||
rg <<<"$choice" '^ssh://([^/]*)/(~/.*)$' -r 'ssh -t "$1" "cd $2; \\$$SHELL -l"' ||:
|
||||
)
|
||||
|
||||
# TODO: evict
|
||||
lru_push() {
|
||||
local line="$1"
|
||||
local fname="$2"
|
||||
mkdir -p "$(dirname "$fname")"
|
||||
local new_lru=$(
|
||||
printf "%s\n" "$line"
|
||||
[[ -s "$fname" ]] && cat "$fname"
|
||||
)
|
||||
uniq-stable <<<"$new_lru" >"$fname"
|
||||
}
|
||||
|
||||
if [[ -n "$ssh_cd_cmd" ]]; then
|
||||
local do_ssh=""
|
||||
local do_ssh=false
|
||||
if gum confirm "SSH in there now?" --default=yes; then
|
||||
do_ssh=1
|
||||
do_ssh=true
|
||||
elif [[ "$?" -eq 130 ]]; then
|
||||
return 1
|
||||
fi
|
||||
# history -s "${zed_cmd}"
|
||||
|
||||
lru_push "$host" "$statedir"/ssh-host-history
|
||||
lru_push "$choice" "$statedir"/ssh-repo-history
|
||||
|
||||
# history -s "${zed_cmd}" # TODO: this too?
|
||||
eval ${zed_cmd}
|
||||
|
||||
# TODO: find a way to not push the rest of the unpushed history
|
||||
history -s "${ssh_cd_cmd}"
|
||||
history -a
|
||||
if [[ -n "$do_ssh" ]]; then
|
||||
if $do_ssh; then
|
||||
printf >&2 "%s\n" "+ ${ssh_cd_cmd}"
|
||||
eval ${ssh_cd_cmd}
|
||||
fi
|
||||
@@ -116,8 +157,13 @@ zeditor-remote() {
|
||||
# https://github.com/zed-industries/zed/issues/4977#issuecomment-2536680602
|
||||
zed_cmd="direnv exec ${choice} $zed_cmd"
|
||||
fi
|
||||
|
||||
# lru_push "$host" "$statedir"/ssh-host-history
|
||||
lru_push "$choice" "$statedir"/ssh-repo-history
|
||||
|
||||
# history -s "${zed_cmd}"
|
||||
eval ${zed_cmd}
|
||||
|
||||
printf >&2 "%s\n" "+$(printf " %q" cd "${choice}")"
|
||||
eval cd "${choice}" # 'eval' to expand the tilde
|
||||
fi
|
||||
|
||||
Reference in New Issue
Block a user