summaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
authorChristoph Schlosser <cschlosser@google.com>2025-06-17 07:23:30 +0000
committerChristoph Schlosser <cschlosser@google.com>2025-06-17 07:23:30 +0000
commitd0e2daac6b87e2977b3782170f138434b22b8a62 (patch)
tree30be7cf6acbf260daf5f72f78885d7a2f3c780b8
parentf311f52b11ac29e3bddf0d0f47316cd06fa0f208 (diff)
downloaddots-d0e2daac6b87e2977b3782170f138434b22b8a62.tar.gz
Add z plugin
-rw-r--r--fish/conf.d/_z-make-portable.fish6
-rw-r--r--fish/conf.d/z.fish63
-rw-r--r--fish/fish_plugins1
-rw-r--r--fish/fish_variables5
-rw-r--r--fish/functions/__z.fish174
-rw-r--r--fish/functions/__z_add.fish49
-rw-r--r--fish/functions/__z_clean.fish11
-rw-r--r--fish/functions/__z_complete.fish13
8 files changed, 321 insertions, 1 deletions
diff --git a/fish/conf.d/_z-make-portable.fish b/fish/conf.d/_z-make-portable.fish
new file mode 100644
index 0000000..fea1758
--- /dev/null
+++ b/fish/conf.d/_z-make-portable.fish
@@ -0,0 +1,6 @@
+# Don't use set -U here
+# set -U stores the actual value of $HOME in the fish_variables file
+# which is tracked in the dotfiles repo.
+# This makes it non-portable.
+set -x Z_DATA $HOME/.local/share/z/data
+set -xp Z_EXCLUDE $HOME
diff --git a/fish/conf.d/z.fish b/fish/conf.d/z.fish
new file mode 100644
index 0000000..59c960f
--- /dev/null
+++ b/fish/conf.d/z.fish
@@ -0,0 +1,63 @@
+if test -z "$Z_DATA"
+ if test -z "$XDG_DATA_HOME"
+ set -U Z_DATA_DIR "$HOME/.local/share/z"
+ else
+ set -U Z_DATA_DIR "$XDG_DATA_HOME/z"
+ end
+ set -U Z_DATA "$Z_DATA_DIR/data"
+end
+
+if test ! -e "$Z_DATA"
+ if test ! -e "$Z_DATA_DIR"
+ mkdir -p -m 700 "$Z_DATA_DIR"
+ end
+ touch "$Z_DATA"
+end
+
+if test -z "$Z_CMD"
+ set -U Z_CMD z
+end
+
+set -U ZO_CMD "$Z_CMD"o
+
+if test ! -z $Z_CMD
+ function $Z_CMD -d "jump around"
+ __z $argv
+ end
+end
+
+if test ! -z $ZO_CMD
+ function $ZO_CMD -d "open target dir"
+ __z -d $argv
+ end
+end
+
+if not set -q Z_EXCLUDE
+ set -U Z_EXCLUDE "^$HOME\$"
+else if contains $HOME $Z_EXCLUDE
+ # Workaround: migrate old default values to a regex (see #90).
+ set Z_EXCLUDE (string replace -r -- "^$HOME\$" '^'$HOME'$$' $Z_EXCLUDE)
+end
+
+# Setup completions once first
+__z_complete
+
+function __z_on_variable_pwd --on-variable PWD
+ __z_add
+end
+
+function __z_uninstall --on-event z_uninstall
+ functions -e __z_on_variable_pwd
+ functions -e $Z_CMD
+ functions -e $ZO_CMD
+
+ if test ! -z "$Z_DATA"
+ printf "To completely erase z's data, remove:\n" >/dev/stderr
+ printf "%s\n" "$Z_DATA" >/dev/stderr
+ end
+
+ set -e Z_CMD
+ set -e ZO_CMD
+ set -e Z_DATA
+ set -e Z_EXCLUDE
+end
diff --git a/fish/fish_plugins b/fish/fish_plugins
index 70035f7..fa25bf1 100644
--- a/fish/fish_plugins
+++ b/fish/fish_plugins
@@ -1,2 +1,3 @@
jorgebucaran/fisher
oh-my-fish/theme-bobthefish
+jethrokuan/z
diff --git a/fish/fish_variables b/fish/fish_variables
index 77c6c0d..4c5987f 100644
--- a/fish/fish_variables
+++ b/fish/fish_variables
@@ -1,9 +1,12 @@
# This file contains fish universal variable definitions.
# VERSION: 3.0
+SETUVAR ZO_CMD:zo
+SETUVAR Z_CMD:z
SETUVAR __fish_initialized:3800
+SETUVAR _fisher_jethrokuan_2F_z_files:\x7e/\x2econfig/fish/functions/__z\x2efish\x1e\x7e/\x2econfig/fish/functions/__z_add\x2efish\x1e\x7e/\x2econfig/fish/functions/__z_clean\x2efish\x1e\x7e/\x2econfig/fish/functions/__z_complete\x2efish\x1e\x7e/\x2econfig/fish/conf\x2ed/z\x2efish
SETUVAR _fisher_jorgebucaran_2F_fisher_files:\x7e/\x2econfig/fish/functions/fisher\x2efish\x1e\x7e/\x2econfig/fish/completions/fisher\x2efish
SETUVAR _fisher_oh_2D_my_2D_fish_2F_theme_2D_bobthefish_files:\x7e/\x2econfig/fish/functions/__bobthefish_colors\x2efish\x1e\x7e/\x2econfig/fish/functions/__bobthefish_display_colors\x2efish\x1e\x7e/\x2econfig/fish/functions/__bobthefish_glyphs\x2efish\x1e\x7e/\x2econfig/fish/functions/bobthefish_display_colors\x2efish\x1e\x7e/\x2econfig/fish/functions/fish_greeting\x2efish\x1e\x7e/\x2econfig/fish/functions/fish_mode_prompt\x2efish\x1e\x7e/\x2econfig/fish/functions/fish_prompt\x2efish\x1e\x7e/\x2econfig/fish/functions/fish_right_prompt\x2efish\x1e\x7e/\x2econfig/fish/functions/fish_title\x2efish
-SETUVAR _fisher_plugins:jorgebucaran/fisher\x1eoh\x2dmy\x2dfish/theme\x2dbobthefish
+SETUVAR _fisher_plugins:jorgebucaran/fisher\x1eoh\x2dmy\x2dfish/theme\x2dbobthefish\x1ejethrokuan/z
SETUVAR _fisher_upgraded_to_4_4:\x1d
SETUVAR fish_color_autosuggestion:brblack
SETUVAR fish_color_cancel:\x2dr
diff --git a/fish/functions/__z.fish b/fish/functions/__z.fish
new file mode 100644
index 0000000..0b02005
--- /dev/null
+++ b/fish/functions/__z.fish
@@ -0,0 +1,174 @@
+function __z -d "Jump to a recent directory."
+ function __print_help -d "Print z help."
+ printf "Usage: $Z_CMD [-celrth] string1 string2...\n\n"
+ printf " -c --clean Removes directories that no longer exist from $Z_DATA\n"
+ printf " -d --dir Opens matching directory using system file manager.\n"
+ printf " -e --echo Prints best match, no cd\n"
+ printf " -l --list List matches and scores, no cd\n"
+ printf " -p --purge Delete all entries from $Z_DATA\n"
+ printf " -r --rank Search by rank\n"
+ printf " -t --recent Search by recency\n"
+ printf " -x --delete Removes the current directory from $Z_DATA\n"
+ printf " -h --help Print this help\n\n"
+ end
+ function __z_legacy_escape_regex
+ # taken from escape_string_pcre2 in fish
+ # used to provide compatibility with fish 2
+ for c in (string split -- '' $argv)
+ if contains $c (string split '' '.^$*+()?[{}\\|-]')
+ printf \\
+ end
+ printf '%s' $c
+ end
+ end
+
+ set -l options h/help c/clean e/echo l/list p/purge r/rank t/recent d/directory x/delete
+
+ argparse $options -- $argv
+
+ if set -q _flag_help
+ __print_help
+ return 0
+ else if set -q _flag_clean
+ __z_clean
+ printf "%s cleaned!\n" $Z_DATA
+ return 0
+ else if set -q _flag_purge
+ echo >$Z_DATA
+ printf "%s purged!\n" $Z_DATA
+ return 0
+ else if set -q _flag_delete
+ sed -i -e "\:^$PWD|.*:d" $Z_DATA
+ return 0
+ end
+
+ set -l typ
+
+ if set -q _flag_rank
+ set typ rank
+ else if set -q _flag_recent
+ set typ recent
+ end
+
+ set -l z_script '
+ function frecent(rank, time) {
+ dx = t-time
+ if( dx < 3600 ) return rank*4
+ if( dx < 86400 ) return rank*2
+ if( dx < 604800 ) return rank/2
+ return rank/4
+ }
+
+ function output(matches, best_match, common) {
+ # list or return the desired directory
+ if( list ) {
+ cmd = "sort -nr"
+ for( x in matches ) {
+ if( matches[x] ) {
+ printf "%-10s %s\n", matches[x], x | cmd
+ }
+ }
+ } else {
+ if( common ) best_match = common
+ print best_match
+ }
+ }
+
+ function common(matches) {
+ # find the common root of a list of matches, if it exists
+ for( x in matches ) {
+ if( matches[x] && (!short || length(x) < length(short)) ) {
+ short = x
+ }
+ }
+ if( short == "/" ) return
+ for( x in matches ) if( matches[x] && index(x, short) != 1 ) {
+ return
+ }
+ return short
+ }
+
+ BEGIN {
+ hi_rank = ihi_rank = -9999999999
+ }
+ {
+ if( typ == "rank" ) {
+ rank = $2
+ } else if( typ == "recent" ) {
+ rank = $3 - t
+ } else rank = frecent($2, $3)
+ if( $1 ~ q ) {
+ matches[$1] = rank
+ } else if( tolower($1) ~ tolower(q) ) imatches[$1] = rank
+ if( matches[$1] && matches[$1] > hi_rank ) {
+ best_match = $1
+ hi_rank = matches[$1]
+ } else if( imatches[$1] && imatches[$1] > ihi_rank ) {
+ ibest_match = $1
+ ihi_rank = imatches[$1]
+ }
+ }
+
+ END {
+ # prefer case sensitive
+ if( best_match ) {
+ output(matches, best_match, common(matches))
+ } else if( ibest_match ) {
+ output(imatches, ibest_match, common(imatches))
+ }
+ }
+ '
+
+ set -l qs
+ for arg in $argv
+ set -l escaped $arg
+ if string escape --style=regex '' >/dev/null 2>&1 # use builtin escape if available
+ set escaped (string escape --style=regex -- $escaped)
+ else
+ set escaped (__z_legacy_escape_regex $escaped)
+ end
+ # Need to escape twice, see https://www.math.utah.edu/docs/info/gawk_5.html#SEC32
+ set escaped (string replace --all -- \\ \\\\ $escaped)
+ set qs $qs $escaped
+ end
+ set -l q (string join -- '.*' $qs)
+
+ if set -q _flag_list
+ # Handle list separately as it can print common path information to stderr
+ # which cannot be captured from a subcommand.
+ command awk -v t=(date +%s) -v list="list" -v typ="$typ" -v q="$q" -F "|" $z_script "$Z_DATA"
+ return
+ end
+
+ set target (command awk -v t=(date +%s) -v typ="$typ" -v q="$q" -F "|" $z_script "$Z_DATA")
+
+ if test "$status" -gt 0
+ return
+ end
+
+ if test -z "$target"
+ printf "'%s' did not match any results\n" "$argv"
+ return 1
+ end
+
+ if set -q _flag_echo
+ printf "%s\n" "$target"
+ else if set -q _flag_directory
+ if test -n "$ZO_METHOD"
+ type -q "$ZO_METHOD"; and "$ZO_METHOD" "$target"; and return $status
+ echo "Cannot open with ZO_METHOD set to $ZO_METHOD"; and return 1
+ else if test "$OS" = Windows_NT
+ # Be careful, in msys2, explorer always return 1
+ type -q explorer; and explorer "$target"
+ return 0
+ echo "Cannot open file explorer"
+ return 1
+ else
+ type -q xdg-open; and xdg-open "$target"; and return $status
+ type -q open; and open "$target"; and return $status
+ echo "Not sure how to open file manager"; and return 1
+ end
+ else
+ pushd "$target"
+ end
+end
diff --git a/fish/functions/__z_add.fish b/fish/functions/__z_add.fish
new file mode 100644
index 0000000..20d5d7e
--- /dev/null
+++ b/fish/functions/__z_add.fish
@@ -0,0 +1,49 @@
+function __z_add -d "Add PATH to .z file"
+ test -n "$fish_private_mode"; and return 0
+
+ for i in $Z_EXCLUDE
+ if string match -r $i $PWD >/dev/null
+ return 0 #Path excluded
+ end
+ end
+
+ set -l tmpfile (mktemp $Z_DATA.XXXXXX)
+
+ if test -f $tmpfile
+ set -l path (string replace --all \\ \\\\ $PWD)
+ command awk -v path=$path -v now=(date +%s) -F "|" '
+ BEGIN {
+ rank[path] = 1
+ time[path] = now
+ }
+ $2 >= 1 {
+ if( $1 == path ) {
+ rank[$1] = $2 + 1
+ time[$1] = now
+ }
+ else {
+ rank[$1] = $2
+ time[$1] = $3
+ }
+ count += $2
+ }
+ END {
+ if( count > 1000 ) {
+ for( i in rank ) print i "|" 0.9*rank[i] "|" time[i] # aging
+ }
+ else for( i in rank ) print i "|" rank[i] "|" time[i]
+ }
+ ' $Z_DATA 2>/dev/null >$tmpfile
+
+ if test ! -z "$Z_OWNER"
+ chown $Z_OWNER:(id -ng $Z_OWNER) $tmpfile
+ end
+ #
+ # Don't use redirection here as it can lead to a race condition where $Z_DATA is clobbered.
+ # Note: There is a still a possible race condition where an old version of $Z_DATA is
+ # read by one instance of Fish before another instance of Fish writes its copy.
+ #
+ command mv $tmpfile $Z_DATA
+ or command rm $tmpfile
+ end
+end
diff --git a/fish/functions/__z_clean.fish b/fish/functions/__z_clean.fish
new file mode 100644
index 0000000..ae1721a
--- /dev/null
+++ b/fish/functions/__z_clean.fish
@@ -0,0 +1,11 @@
+function __z_clean -d "Clean up .z file to remove paths no longer valid"
+ set -l tmpfile (mktemp $Z_DATA.XXXXXX)
+
+ if test -f $tmpfile
+ while read line
+ set -l path (string split '|' $line)[1]
+ test -d $path; and echo $line
+ end <$Z_DATA >$tmpfile
+ command mv -f $tmpfile $Z_DATA
+ end
+end
diff --git a/fish/functions/__z_complete.fish b/fish/functions/__z_complete.fish
new file mode 100644
index 0000000..a626456
--- /dev/null
+++ b/fish/functions/__z_complete.fish
@@ -0,0 +1,13 @@
+function __z_complete -d "add completions"
+ complete -c $Z_CMD -a "(__z -l | string replace -r '^\\S*\\s*' '')" -f -k
+ complete -c $ZO_CMD -a "(__z -l | string replace -r '^\\S*\\s*' '')" -f -k
+
+ complete -c $Z_CMD -s c -l clean -d "Cleans out $Z_DATA"
+ complete -c $Z_CMD -s e -l echo -d "Prints best match, no cd"
+ complete -c $Z_CMD -s l -l list -d "List matches, no cd"
+ complete -c $Z_CMD -s p -l purge -d "Purges $Z_DATA"
+ complete -c $Z_CMD -s r -l rank -d "Searches by rank, cd"
+ complete -c $Z_CMD -s t -l recent -d "Searches by recency, cd"
+ complete -c $Z_CMD -s h -l help -d "Print help"
+ complete -c $Z_CMD -s x -l delete -d "Removes the current directory from $Z_DATA"
+end