fisher/fisher.fish
Jorge Bucaran c9974efc12
feat: add user key bindings autoloading for legacy fish
Packages add key bindings via configuration snippets. As it turns out,
configuration snippets are not available  in legacy fish and only fish
3.0 or newer can use the bind builtin on shell startup.

To support  packages that add key  bindings in legacy fish,  we make a
copy  of  the  user's  fish_user_key_bindings function  (if  there  it
exists)  and create  a new  one that  when called,  will source  every
*_key_bindings.fish  inside \$fisher_path/conf.d  and finally  run our
copy of the  user's fish_user_key_bindings, if it  exists, to preserve
any user defined key bindings.

To be able to  run code on shell startup we  create a fisher.fish file
inside \$fisher_path/conf.d if running an  older version of fish. When
the user  upgrades to  a supported  fish version,  we'll automatically
remove this file.
2018-10-17 15:51:12 +09:00

445 lines
15 KiB
Fish

set -g fisher_version 3.0.9
type source >/dev/null; or function source; . $argv; end
switch (command uname)
case Darwin FreeBSD
function _fisher_now -a elapsed
command perl -MTime::HiRes -e 'printf("%.0f\n", (Time::HiRes::time() * 1000) - $ARGV[0])' $elapsed
end
case \*
function _fisher_now -a elapsed
command date "+%s%3N" | command awk -v ELAPSED="$elapsed" '{ sub(/%?3N$/, "000") } $0 -= ELAPSED'
end
end
function fisher -a cmd -d "fish package manager"
if not command which curl >/dev/null
echo "curl is required to use fisher -- install curl and try again" >&2
return 1
end
test -z "$XDG_CACHE_HOME"; and set XDG_CACHE_HOME ~/.cache
test -z "$XDG_CONFIG_HOME"; and set XDG_CONFIG_HOME ~/.config
set -g fish_config $XDG_CONFIG_HOME/fish
set -g fisher_cache $XDG_CACHE_HOME/fisher
set -g fisher_config $XDG_CONFIG_HOME/fisher
test -z "$fisher_path"; and set -g fisher_path $fish_config
command mkdir -p {$fish_config,$fisher_path}/{functions,completions,conf.d} $fisher_cache
if test ! -e "$fisher_path/completions/fisher.fish"
echo "fisher self-complete" > $fisher_path/completions/fisher.fish
_fisher_self_complete
end
if test -e "$fisher_path/conf.d/fisher.fish"
command rm -f $fisher_path/conf.d/fisher.fish
end
switch "$version"
case \*-\*
case 2\*
echo "fisher copy-user-key-bindings" > $fisher_path/conf.d/fisher.fish
end
switch "$cmd"
case self-complete
_fisher_self_complete
case copy-user-key-bindings
_fisher_copy_user_key_bindings
case ls
_fisher_ls | command sed "s|$HOME|~|"
case -v {,--}version
_fisher_version (status -f)
case -h {,--}help
_fisher_help
case self-update
_fisher_self_update (status -f); or return
_fisher_self_complete
case self-uninstall
_fisher_self_uninstall
case add rm ""
if test ! -z "$argv"
if not isatty
while read -l i
set argv $argv $i
end
end
end
_fisher_commit $argv; or return
_fisher_self_complete
case \*
echo "error: unknown flag or command \"$cmd\"" >&2
_fisher_help >&2
return 1
end
end
function _fisher_self_complete
complete -c fisher --erase
complete -xc fisher -n __fish_use_subcommand -a version -d "Show version"
complete -xc fisher -n __fish_use_subcommand -a help -d "Show help"
complete -xc fisher -n __fish_use_subcommand -a self-update -d "Update fisher"
complete -xc fisher -n __fish_use_subcommand -a ls -d "List installed packages"
complete -xc fisher -n __fish_use_subcommand -a rm -d "Remove packages"
complete -xc fisher -n __fish_use_subcommand -a add -d "Add packages"
for pkg in (_fisher_ls)
complete -xc fisher -n "__fish_seen_subcommand_from rm" -a $pkg
end
end
function _fisher_copy_user_key_bindings
if functions -q fish_user_key_bindings
functions -c fish_user_key_bindings fish_user_key_bindings_copy
end
function fish_user_key_bindings
for file in $fisher_path/conf.d/*_key_bindings.fish
source $file >/dev/null 2>/dev/null
end
if functions -q fish_user_key_bindings_copy
fish_user_key_bindings_copy
end
end
end
function _fisher_ls
set -l pkgs $fisher_config/*/*/*
for pkg in $pkgs
command readlink $pkg; and continue; or echo $pkg
end | command sed "s|$fisher_config/*||;s|github\.com/||"
end
function _fisher_version -a file
echo "fisher version $fisher_version $file" | command sed "s|$HOME|~|"
end
function _fisher_help
echo "usage: "
echo " fisher add <PACKAGES> add packages"
echo " fisher rm <PACKAGES> remove packages"
echo " fisher update installed packages"
echo " fisher ls show installed packages"
echo " fisher help show this help"
echo " fisher version show version"
echo " fisher self-update update fisher"
echo " fisher self-uninstall uninstall fisher"
echo
echo "examples:"
echo " fisher add jethrokuan/z rafaelrinaldi/pure"
echo " fisher add gitlab.com/owner/foobar@v2"
echo " fisher add ~/path/to/myfish/pkg"
echo " fisher rm rafaelrinaldi/pure"
echo " fisher ls | fisher rm"
echo " fisher add < bundle"
end
function _fisher_self_update -a file
set -l url "https://raw.githubusercontent.com/jorgebucaran/fisher/master/fisher.fish"
echo "fetching $url" >&2
curl -s "$url?nocache" >$file@
set -l next_version (awk 'NR == 1 { print $4; exit }' < $file@)
switch "$next_version"
case "" $fisher_version
command rm -f $file@
if test -z "$next_version"
echo "cannot update fisher -- are you offline?" >&2
return 1
end
echo "fisher is already up-to-date" >&2
case \*
echo "linking $file" | command sed "s|$HOME|~|" >&2
command mv -f $file@ $file
source $file
echo "updated fisher to $fisher_version -- hooray!" >&2
end
end
function _fisher_self_uninstall
set -l current_pkgs $fisher_config/*/*/*
for path in $fisher_cache (_fisher_pkg_remove_all $current_pkgs) $fisher_config $fisher_path/{functions,completions,conf.d}/fisher.fish $fish_config/fishfile
echo "removing $path"
command rm -rf $path 2>/dev/null
end | command sed "s|$HOME|~|" >&2
set -e fisher_cache
set -e fisher_config
set -e fisher_path
set -e fisher_version
complete -c fisher --erase
functions -e (functions -a | command awk '/^_fisher/') fisher
echo "done -- see you again!" >&2
end
function _fisher_commit
set -l elapsed (_fisher_now)
set -l current_pkgs $fisher_config/*/*/*
set -l removed_pkgs (_fisher_pkg_remove_all $current_pkgs)
command rm -rf $fisher_config
command mkdir -p $fisher_config
set -l fishfile $fish_config/fishfile
if test ! -e "$fishfile"
command touch $fishfile
echo "created empty fishfile in $fishfile" | command sed "s|$HOME|~|" >&2
end
printf "%s\n" (_fisher_fishfile_format (echo -s $argv\;) < $fishfile) > $fishfile
set -l expected_pkgs (_fisher_fishfile_read < $fishfile)
set -l added_pkgs (_fisher_pkg_fetch_all $expected_pkgs)
set -l updated_pkgs (
for pkg in $removed_pkgs
set pkg (echo $pkg | command sed "s|$fisher_config/*||")
if contains -- $pkg $added_pkgs
echo $pkg
end
end)
if test -z "$added_pkgs$updated_pkgs$removed_pkgs$expected_pkgs"
echo "nothing to commit -- try adding some packages" >&2
return 1
end
_fisher_status (count $added_pkgs) (count $updated_pkgs) (count $removed_pkgs) (_fisher_now $elapsed) >&2
end
function _fisher_pkg_remove_all
for pkg in $argv
echo $pkg
_fisher_pkg_uninstall $pkg
end
end
function _fisher_pkg_fetch_all
set -l pkg_jobs
set -l local_pkgs
set -l actual_pkgs
set -l expected_pkgs
for id in $argv
switch $id
case \~\* /\*
set -l path (echo "$id" | command sed "s|~|$HOME|")
if test -e "$path"
set local_pkgs $local_pkgs $path
else
echo "cannot install \"$id\" -- is this a valid file?" >&2
end
continue
end
command awk -v ID=$id -v FS=/ 'BEGIN {
if (split(ID, tmp, /@+|:/) > 2) {
if (tmp[4]) sub("@"tmp[4], "", ID)
print ID "\t" tmp[2]"/"tmp[1]"/"tmp[3] "\t" (tmp[4] ? tmp[4] : "master")
} else {
pkg = split(ID, _, "/") <= 2 ? "github.com/"tmp[1] : tmp[1]
tag = tmp[2] ? tmp[2] : "master"
print (\
pkg ~ /^github/ ? "https://codeload."pkg"/tar.gz/"tag : \
pkg ~ /^gitlab/ ? "https://"pkg"/-/archive/"tag"/"tmp[split(pkg, tmp, "/")]"-"tag".tar.gz" : \
pkg ~ /^bitbucket/ ? "https://"pkg"/get/"tag".tar.gz" : pkg \
) "\t" pkg
}
}' | read -l url pkg tag
if test ! -d "$fisher_config/$pkg"
fish -c "
echo fetching $url >&2
command mkdir -p \"$fisher_config/$pkg\"
if test ! -z \"$tag\"
command git clone $url \"$fisher_config/$pkg\" --branch $tag --depth 1 2>/dev/null
or echo cannot clone \"$url\" -- is this a valid url\? >&2
else if command curl -Ss $url 2>&1 | command tar -xzf- -C \"$fisher_config/$pkg\" --strip-components=1 2>/dev/null
command mkdir -p \"$fisher_cache/$pkg\"
command cp -Rf \"$fisher_config/$pkg\" \"$fisher_cache/$pkg/..\"
else if test -d \"$fisher_cache/$pkg\"
echo cannot connect to server -- searching in \"$fisher_cache/$pkg\" | command sed 's|$HOME|~|' >&2
command cp -Rf \"$fisher_cache/$pkg\" \"$fisher_config/$pkg/..\"
else
command rm -rf \"$fisher_config/$pkg\"
echo cannot install \"$pkg\" -- is this a valid package\? >&2
end
" >/dev/null &
set pkg_jobs $pkg_jobs (_fisher_jobs --last)
set expected_pkgs $expected_pkgs "$pkg"
end
end
if test ! -z "$pkg_jobs"
_fisher_wait $pkg_jobs
for pkg in $expected_pkgs
if test -d "$fisher_config/$pkg"
set actual_pkgs $actual_pkgs $pkg
_fisher_pkg_install $fisher_config/$pkg
end
end
end
for pkg in $local_pkgs
set -l path local/$USER
set -l name (command basename $pkg)
command mkdir -p $fisher_config/$path
command ln -sf $pkg $fisher_config/$path
set actual_pkgs $actual_pkgs $path/$name
_fisher_pkg_install $fisher_config/$path/$name
end
if test ! -z "$actual_pkgs"
_fisher_pkg_fetch_all (_fisher_pkg_get_deps $actual_pkgs | command sort --unique)
printf "%s\n" $actual_pkgs
end
end
function _fisher_pkg_get_deps
for pkg in $argv
set -l path $fisher_config/$pkg
if test ! -d "$path"
echo $pkg
else if test -s "$path/fishfile"
_fisher_pkg_get_deps (_fisher_fishfile_format < $path/fishfile | _fisher_fishfile_read)
end
end
end
function _fisher_pkg_install -a pkg
set -l name (command basename $pkg)
set -l files $pkg/{functions,completions,conf.d}/**.* $pkg/*.fish
for source in $files
set -l target (command basename $source)
switch $source
case $pkg/conf.d\*
set target $fisher_path/conf.d/$target
case $pkg/completions\*
set target $fisher_path/completions/$target
case $pkg/{functions,}\*
switch $target
case uninstall.fish
continue
case init.fish key_bindings.fish
set target $fisher_path/conf.d/$name\_$target
case \*
set target $fisher_path/functions/$target
end
end
echo "linking $target" | command sed "s|$HOME|~|" >&2
command cp -f $source $target
switch $target
case \*.fish
source $target >/dev/null 2>/dev/null
end
end
end
function _fisher_pkg_uninstall -a pkg
set -l name (command basename $pkg)
set -l files $pkg/{conf.d,completions,functions}/**.* $pkg/*.fish
for source in $files
set -l target (command basename $source)
set -l filename (command basename $target .fish)
switch $source
case $pkg/conf.d\*
test "$filename.fish" = "$target"; and emit "$filename"_uninstall
set target conf.d/$target
case $pkg/completions\*
test "$filename.fish" = "$target"; and complete -ec $filename
set target completions/$target
case $pkg/{,functions}\*
test "$filename.fish" = "$target"; and functions -e $filename
switch $target
case uninstall.fish
source $source
continue
case init.fish key_bindings.fish
set target conf.d/$name\_$target
case \*
set target functions/$target
end
end
command rm -f $fisher_path/$target
end
if not functions -q fish_prompt
source "$__fish_datadir$__fish_data_dir/functions/fish_prompt.fish"
end
end
function _fisher_fishfile_read
command awk -v FS=\# '!/^#/ && NF { print $1 }'
end
function _fisher_fishfile_format -a pkgs
command awk -v PWD=$PWD -v HOME=$HOME -v PKGS="$pkgs" '
BEGIN {
pkg_count = split(PKGS, pkgs, ";") - 1
cmd = pkgs[1]
for (i = 2; i <= pkg_count; i++) {
pkg_ids[i - 1] = get_pkg_id( pkgs[i] = normalize(pkgs[i]) )
}
} {
if (NF) {
$0 = normalize($0)
newln = newln > 0 ? "" : newln
if (/^#/) print newln$0
else if (!seen[(pkg_id = get_pkg_id($0))]++) {
for (i = 1; i < pkg_count; i++) {
if (pkg_ids[i] == pkg_id) {
if (cmd == "rm") next
$0 = pkgs[i + 1]
break
}
}
print newln$0
}
newln = NF
} else if (newln) newln = "\n"(newln > 0 ? "" : newln)
}
END {
if (cmd == "rm" || pkg_count <= 1) exit
for (i = 2; i <= pkg_count; i++) {
if (!seen[pkg_ids[i - 1]]) print pkgs[i]
}
}
function normalize(s) {
gsub(/^[ \t]*(https?:\/\/)?(github\.com\/)?|[\/ \t]*$/, "", s)
sub(/^\.\//, PWD"/", s)
sub(HOME, "~", s)
return s
}
function get_pkg_id(s) {
return (split(s, tmp, /@+|:/) > 2) ? tmp[2]"/"tmp[1]"/"tmp[3] : tmp[1]
}
'
end
function _fisher_status -a added updated removed elapsed
command awk -v ADDED=$added -v UPDATED=$updated -v REMOVED=$removed -v ELAPSED=$elapsed '
BEGIN {
if (ADDED = ADDED - UPDATED) res = msg(res, "added", ADDED)
if (UPDATED) res = msg(res, "updated", UPDATED)
if (REMOVED = REMOVED - UPDATED) res = msg(res, "removed", REMOVED)
printf((res ? res : "done") " in %.2fs\n", ELAPSED / 1000)
}
function msg(res, str, n) {
return (res ? res ", " : "") str " " n " package" (n > 1 ? "s" : "")
}
'
end
function _fisher_jobs
jobs $argv | command awk '/[0-9]+\t/ { print $1 }'
end
function _fisher_wait
while for job in $argv
contains -- $job (_fisher_jobs); and break
end
end
end