From 613045e7e565a9069568e7f94574701c3d440489 Mon Sep 17 00:00:00 2001 From: Marc Cornellà Date: Mon, 17 Aug 2020 21:47:39 +0200 Subject: Underline links in install and update script --- tools/install.sh | 12 +++++++++--- tools/upgrade.sh | 8 +++++--- 2 files changed, 14 insertions(+), 6 deletions(-) diff --git a/tools/install.sh b/tools/install.sh index 8a93708ca..0fc554786 100755 --- a/tools/install.sh +++ b/tools/install.sh @@ -53,6 +53,10 @@ error() { echo ${RED}"Error: $@"${RESET} >&2 } +underline() { + echo "$(printf '\033[4m')$@$(printf '\033[24m')" +} + setup_color() { # Only use colors if connected to a terminal if [ -t 1 ]; then @@ -269,11 +273,13 @@ main() { /____/ ....is now installed! + EOF + cat <<-EOF Before you scream Oh My Zsh! please look over the ~/.zshrc file to select plugins, themes, and options. - • Follow us on Twitter: https://twitter.com/ohmyzsh - • Join our Discord server: https://discord.gg/ohmyzsh - • Get stickers, shirts, coffee mugs and other swag: https://shop.planetargon.com/collections/oh-my-zsh + • Follow us on Twitter: $(underline https://twitter.com/ohmyzsh) + • Join our Discord server: $(underline https://discord.gg/ohmyzsh) + • Get stickers, shirts, coffee mugs and other swag: $(underline https://shop.planetargon.com/collections/oh-my-zsh) EOF printf "$RESET" diff --git a/tools/upgrade.sh b/tools/upgrade.sh index de7a8419b..a71dec88b 100644 --- a/tools/upgrade.sh +++ b/tools/upgrade.sh @@ -14,6 +14,7 @@ if [ -t 1 ]; then YELLOW=$(printf '\033[33m') BLUE=$(printf '\033[34m') BOLD=$(printf '\033[1m') + UNDER=$(printf '\033[4m') RESET=$(printf '\033[m') else RB_RED="" @@ -28,6 +29,7 @@ else GREEN="" YELLOW="" BLUE="" + UNDER="" BOLD="" RESET="" fi @@ -62,9 +64,9 @@ then printf '%s\____/%s_/ /_/ %s /_/ /_/ /_/%s\__, / %s /___/%s____/%s_/ /_/ %s\n' $RB_RED $RB_ORANGE $RB_YELLOW $RB_GREEN $RB_BLUE $RB_INDIGO $RB_VIOLET $RB_RESET printf '%s %s %s %s /____/ %s %s %s %s\n' $RB_RED $RB_ORANGE $RB_YELLOW $RB_GREEN $RB_BLUE $RB_INDIGO $RB_VIOLET $RB_RESET printf "${BLUE}%s\n" "Hooray! Oh My Zsh has been updated and/or is at the current version." - printf "${BLUE}${BOLD}%s${RESET}\n" "To keep up on the latest news and updates, follow us on Twitter: https://twitter.com/ohmyzsh" - printf "${BLUE}${BOLD}%s${RESET}\n" "Want to get involved in the community? Join our Discord: https://discord.gg/ohmyzsh" - printf "${BLUE}${BOLD}%s${RESET}\n" "Get your Oh My Zsh swag at: https://shop.planetargon.com/collections/oh-my-zsh" + printf "${BLUE}${BOLD}%s ${UNDER}%s${RESET}\n" "To keep up on the latest news and updates, follow us on Twitter:" "https://twitter.com/ohmyzsh" + printf "${BLUE}${BOLD}%s ${UNDER}%s${RESET}\n" "Want to get involved in the community? Join our Discord:" "https://discord.gg/ohmyzsh" + printf "${BLUE}${BOLD}%s ${UNDER}%s${RESET}\n" "Get your Oh My Zsh swag at:" "https://shop.planetargon.com/collections/oh-my-zsh" else printf "${RED}%s${RESET}\n" 'There was an error updating. Try again later?' fi -- cgit v1.2.3-70-g09d2 From 7deda85f8cf7fb3c2f36b771a2e8bd70a28bf0b3 Mon Sep 17 00:00:00 2001 From: Marc Cornellà Date: Mon, 17 Aug 2020 22:11:02 +0200 Subject: Fix upgrade_oh_my_zsh function deprecation --- lib/cli.zsh | 4 ++++ lib/functions.zsh | 16 ++++++++++++++-- 2 files changed, 18 insertions(+), 2 deletions(-) diff --git a/lib/cli.zsh b/lib/cli.zsh index 3c09cdab8..d8203f271 100644 --- a/lib/cli.zsh +++ b/lib/cli.zsh @@ -201,10 +201,14 @@ function _omz::pr::test { } function _omz::update { + # Run update script env ZSH="$ZSH" sh "$ZSH/tools/upgrade.sh" # Update last updated file zmodload zsh/datetime echo "LAST_EPOCH=$(( EPOCHSECONDS / 60 / 60 / 24 ))" >! "${ZSH_CACHE_DIR}/.zsh-update" # Remove update lock if it exists command rm -rf "$ZSH/log/update.lock" + # Restart the zsh session + _omz::log info "restarting the zsh session..." + [[ -z "$SHELL" ]] && exec ${SHELL#-} || exec zsh } diff --git a/lib/functions.zsh b/lib/functions.zsh index e85b867a5..0556aeb29 100644 --- a/lib/functions.zsh +++ b/lib/functions.zsh @@ -7,8 +7,20 @@ function uninstall_oh_my_zsh() { } function upgrade_oh_my_zsh() { - echo >&2 "${fg[yellow]}Note: \`$0\` is deprecated. Use \`omz update\` instead.$reset_color" - omz update + if (( $+functions[_omz::update] )); then + echo >&2 "${fg[yellow]}Note: \`$0\` is deprecated. Use \`omz update\` instead.$reset_color" + fi + + # Run update script + env ZSH="$ZSH" sh "$ZSH/tools/upgrade.sh" + # Update last updated file + zmodload zsh/datetime + echo "LAST_EPOCH=$(( EPOCHSECONDS / 60 / 60 / 24 ))" >! "${ZSH_CACHE_DIR}/.zsh-update" + # Remove update lock if it exists + command rm -rf "$ZSH/log/update.lock" + # Restart the zsh session + _omz::log info "restarting the zsh session..." + [[ -z "$SHELL" ]] && exec ${SHELL#-} || exec zsh } function take() { -- cgit v1.2.3-70-g09d2 From 89400f156a6e1d64acaeab9ec265f54cdab91817 Mon Sep 17 00:00:00 2001 From: Marc Cornellà Date: Tue, 18 Aug 2020 19:51:19 +0200 Subject: Remove zsh session restart on omz update and upgrade_oh_my_zsh --- lib/cli.zsh | 3 --- lib/functions.zsh | 3 --- 2 files changed, 6 deletions(-) diff --git a/lib/cli.zsh b/lib/cli.zsh index d8203f271..c1ae2bdf2 100644 --- a/lib/cli.zsh +++ b/lib/cli.zsh @@ -208,7 +208,4 @@ function _omz::update { echo "LAST_EPOCH=$(( EPOCHSECONDS / 60 / 60 / 24 ))" >! "${ZSH_CACHE_DIR}/.zsh-update" # Remove update lock if it exists command rm -rf "$ZSH/log/update.lock" - # Restart the zsh session - _omz::log info "restarting the zsh session..." - [[ -z "$SHELL" ]] && exec ${SHELL#-} || exec zsh } diff --git a/lib/functions.zsh b/lib/functions.zsh index 0556aeb29..4bc533b7a 100644 --- a/lib/functions.zsh +++ b/lib/functions.zsh @@ -18,9 +18,6 @@ function upgrade_oh_my_zsh() { echo "LAST_EPOCH=$(( EPOCHSECONDS / 60 / 60 / 24 ))" >! "${ZSH_CACHE_DIR}/.zsh-update" # Remove update lock if it exists command rm -rf "$ZSH/log/update.lock" - # Restart the zsh session - _omz::log info "restarting the zsh session..." - [[ -z "$SHELL" ]] && exec ${SHELL#-} || exec zsh } function take() { -- cgit v1.2.3-70-g09d2 From 79614908ec5fc6af1151f7c2a17a43844f920e66 Mon Sep 17 00:00:00 2001 From: Marc Cornellà Date: Thu, 20 Aug 2020 11:15:48 +0200 Subject: meta: don't apply support label on bug reports Enough users have complained about the bug label not being applied instead. If there is no label applied this won't be complained about so often. --- .github/ISSUE_TEMPLATE/bug_report.md | 1 - 1 file changed, 1 deletion(-) diff --git a/.github/ISSUE_TEMPLATE/bug_report.md b/.github/ISSUE_TEMPLATE/bug_report.md index b7f5460c1..611d8ebae 100644 --- a/.github/ISSUE_TEMPLATE/bug_report.md +++ b/.github/ISSUE_TEMPLATE/bug_report.md @@ -1,7 +1,6 @@ --- name: Bug report about: Create a report to help us improve Oh My Zsh -labels: 'Type: support' --- -- cgit v1.2.3-70-g09d2 From 93f8c0686cd28d3f93810c4824397f21530899c7 Mon Sep 17 00:00:00 2001 From: "Adam G. Emerson" <35545129+AdamGEmerson@users.noreply.github.com> Date: Thu, 20 Aug 2020 04:26:39 -0500 Subject: docs: add fetch install command for FreeBSD (#9172) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Co-authored-by: Marc Cornellà --- README.md | 6 ++++++ tools/install.sh | 4 +++- 2 files changed, 9 insertions(+), 1 deletion(-) diff --git a/README.md b/README.md index 329eff178..8dfa4a139 100644 --- a/README.md +++ b/README.md @@ -43,6 +43,12 @@ sh -c "$(curl -fsSL https://raw.githubusercontent.com/ohmyzsh/ohmyzsh/master/too sh -c "$(wget -O- https://raw.githubusercontent.com/ohmyzsh/ohmyzsh/master/tools/install.sh)" ``` +#### via fetch + +```shell +sh -c "$(fetch -o - https://raw.githubusercontent.com/ohmyzsh/ohmyzsh/master/tools/install.sh)" +``` + #### Manual inspection It's a good idea to inspect the install script from projects you don't yet know. You can do diff --git a/tools/install.sh b/tools/install.sh index 0fc554786..4e166fef4 100755 --- a/tools/install.sh +++ b/tools/install.sh @@ -2,8 +2,10 @@ # # This script should be run via curl: # sh -c "$(curl -fsSL https://raw.githubusercontent.com/ohmyzsh/ohmyzsh/master/tools/install.sh)" -# or wget: +# or via wget: # sh -c "$(wget -qO- https://raw.githubusercontent.com/ohmyzsh/ohmyzsh/master/tools/install.sh)" +# or via fetch: +# sh -c "$(fetch -o - https://raw.githubusercontent.com/ohmyzsh/ohmyzsh/master/tools/install.sh)" # # As an alternative, you can first download the install script and run it afterwards: # wget https://raw.githubusercontent.com/ohmyzsh/ohmyzsh/master/tools/install.sh -- cgit v1.2.3-70-g09d2 From 03b6a72576cb0f0b53abde25d409e8026d5c4ee9 Mon Sep 17 00:00:00 2001 From: Marc Cornellà Date: Thu, 20 Aug 2020 11:28:10 +0200 Subject: docs: document oneline argument passing to install script --- tools/install.sh | 2 ++ 1 file changed, 2 insertions(+) diff --git a/tools/install.sh b/tools/install.sh index 4e166fef4..61b21cb9e 100755 --- a/tools/install.sh +++ b/tools/install.sh @@ -32,6 +32,8 @@ # --keep-zshrc: sets KEEP_ZSHRC to 'yes' # For example: # sh install.sh --unattended +# or: +# sh -c "$(curl -fsSL https://raw.githubusercontent.com/ohmyzsh/ohmyzsh/master/tools/install.sh)" "" --unattended # set -e -- cgit v1.2.3-70-g09d2 From c785db621e8bdb9edb897cc7353fe87fb6e550b1 Mon Sep 17 00:00:00 2001 From: Kirill Suslov Date: Sun, 23 Aug 2020 15:52:11 -0400 Subject: Update README with new update command (#9203) Note: `upgrade_oh_my_zsh` is deprecated. Use `omz update` instead. --- README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/README.md b/README.md index 8dfa4a139..8be05b6f1 100644 --- a/README.md +++ b/README.md @@ -273,7 +273,7 @@ DISABLE_AUTO_UPDATE=true If you'd like to upgrade at any point in time (maybe someone just released a new plugin and you don't want to wait a week?) you just need to run: ```shell -upgrade_oh_my_zsh +omz update ``` Magic! 🎉 -- cgit v1.2.3-70-g09d2 From cd17aed9e109692273b5c251414ee733cba704f2 Mon Sep 17 00:00:00 2001 From: daddeffe Date: Sun, 23 Aug 2020 22:58:08 +0200 Subject: sudo: keep space before the command to ignore it in the history (#9178) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Co-authored-by: Marc Cornellà --- plugins/sudo/sudo.plugin.zsh | 11 +++++++++++ 1 file changed, 11 insertions(+) diff --git a/plugins/sudo/sudo.plugin.zsh b/plugins/sudo/sudo.plugin.zsh index 51579377d..b3749eff7 100644 --- a/plugins/sudo/sudo.plugin.zsh +++ b/plugins/sudo/sudo.plugin.zsh @@ -14,6 +14,14 @@ sudo-command-line() { [[ -z $BUFFER ]] && LBUFFER="$(fc -ln -1)" + + # Save beginning space + local WHITESPACE="" + if [[ ${LBUFFER:0:1} == " " ]] ; then + WHITESPACE=" " + LBUFFER="${LBUFFER:1}" + fi + if [[ -n $EDITOR && $BUFFER == $EDITOR\ * ]]; then if [[ ${#LBUFFER} -le ${#EDITOR} ]]; then RBUFFER=" ${BUFFER#$EDITOR }" @@ -38,6 +46,9 @@ sudo-command-line() { else LBUFFER="sudo $LBUFFER" fi + + # Preserve beginning space + LBUFFER="${WHITESPACE}${LBUFFER}" } zle -N sudo-command-line # Defined shortcut keys: [Esc] [Esc] -- cgit v1.2.3-70-g09d2 From cfb86cd08d3b24fd4b59d0d35b3af1f589c891fa Mon Sep 17 00:00:00 2001 From: Marc Cornellà Date: Mon, 24 Aug 2020 17:48:39 +0200 Subject: zsh_reload: use $SHELL to reload zsh only if it's a zsh shell (fixes #9054) --- plugins/zsh_reload/zsh_reload.plugin.zsh | 14 ++++++++++++-- 1 file changed, 12 insertions(+), 2 deletions(-) diff --git a/plugins/zsh_reload/zsh_reload.plugin.zsh b/plugins/zsh_reload/zsh_reload.plugin.zsh index 83f8da733..0d29a7ce3 100644 --- a/plugins/zsh_reload/zsh_reload.plugin.zsh +++ b/plugins/zsh_reload/zsh_reload.plugin.zsh @@ -7,6 +7,16 @@ src() { zrecompile -p $f && command rm -f $f.zwc.old done - # Use $SHELL if available; remove leading dash if login shell - [[ -n "$SHELL" ]] && exec ${SHELL#-} || exec zsh + # Use $SHELL if it's available and a zsh shell + local shell="$ZSH_ARGZERO" + if [[ "${${SHELL:t}#-}" = zsh ]]; then + shell="$SHELL" + fi + + # Remove leading dash if login shell and run accordingly + if [[ "${shell:0:1}" = "-" ]]; then + exec -l "${shell#-}" + else + exec "$shell" + fi } -- cgit v1.2.3-70-g09d2 From 8d08f1634a7b9782e3722ce770e8630f569afe3f Mon Sep 17 00:00:00 2001 From: Pavol Juhas Date: Thu, 27 Aug 2020 00:44:25 -0700 Subject: scd: update to 1.4.0 (#9066) --- plugins/scd/README.md | 54 +++++-- plugins/scd/_scd | 60 ++++++++ plugins/scd/scd | 343 ++++++++++++++++++++++++++++++++------------- plugins/scd/scd.plugin.zsh | 16 +-- 4 files changed, 355 insertions(+), 118 deletions(-) create mode 100644 plugins/scd/_scd mode change 100644 => 100755 plugins/scd/scd diff --git a/plugins/scd/README.md b/plugins/scd/README.md index 8c156da1f..d8535f9f2 100644 --- a/plugins/scd/README.md +++ b/plugins/scd/README.md @@ -14,8 +14,9 @@ directory aliases, which appear as named directories in zsh session. ## INSTALLATION NOTES Besides oh-my-zsh, `scd` can be used with *bash*, *dash* or *tcsh* -shells and is also available as [Vim](https://www.vim.org/) plugin and -[IPython](https://ipython.org/) extension. For installation details, see +shells and is also available as Vim plugin +[scd.vim](https://github.com/pavoljuhas/scd.vim) and +[IPython](https://ipython.org) extension. For installation details, see https://github.com/pavoljuhas/smart-change-directory. ## SYNOPSIS @@ -24,11 +25,31 @@ https://github.com/pavoljuhas/smart-change-directory. scd [options] [pattern1 pattern2 ...] ``` +## PATTERNS + +Patterns may use all zsh [glob operators]( +http://zsh.sourceforge.net/Doc/Release/Expansion.html#Glob-Operators) +available with *extendedglob* option. Specified patterns must match +the absolute path and at least one of them must match in the tail. +Several special patterns are also recognized as follows: + +
+^PAT
+ PAT must match at the beginning of the path, for example, "^/home"
+PAT$
+ require PAT to match the end of the path, "man$"
+./
+ match only subdirectories of the current directory
+:PAT
+ require PAT to match over the tail component, ":doc", ":re/doc"
+
+ + ## OPTIONS
-a, --add
- add specified directories to the directory index.
+ add current or specified directories to the directory index.
--unindex
remove current or specified directories from the index.
@@ -42,11 +63,16 @@ scd [options] [pattern1 pattern2 ...] --unalias
remove ALIAS definition for the current or specified directory from - ~/.scdalias.zsh.
+ ~/.scdalias.zsh. Use "OLD" to purge aliases to non-existent + directories.
-A, --all
- include all matching directories. Disregard matching by directory - alias and filtering of less likely paths.
+ display all directories even those excluded by patterns in + ~/.scdignore. Disregard the unique matching for a + directory alias and filtering of less likely paths.
+ +-p, --push
+ use "pushd" to change to the target directory.
--list
show matching directories and exit.
@@ -58,6 +84,7 @@ scd [options] [pattern1 pattern2 ...] display this options summary and exit.
+ ## Examples ```sh @@ -83,17 +110,26 @@ scd --alias=xray scd xray ``` -# FILES +## FILES
~/.scdhistory
time-stamped index of visited directories.
~/.scdalias.zsh
- scd-generated definitions of directory aliases.
+ scd-generated definitions of directory aliases.
+ +~/.scdignore
+ + glob patterns for paths to be ignored in the scd search, for example, + /mnt/backup/*. The patterns are specified one per line + and are matched assuming the extendedglob zsh option. Lines + starting with "#" are skipped as comments. The .scdignore patterns + are not applied in the --all mode.
-# ENVIRONMENT + +## ENVIRONMENT
SCD_HISTFILE
diff --git a/plugins/scd/_scd b/plugins/scd/_scd new file mode 100644 index 000000000..39c7fa463 --- /dev/null +++ b/plugins/scd/_scd @@ -0,0 +1,60 @@ +#compdef scd +#description smart change directory + +local curcontext="$curcontext" state line expl ret=1 +typeset -A opt_args + +local -a indexopts myargs +indexopts=( --add -a --unindex ) + +myargs=( + # common options + "(--help -h)"{--help,-h}"[print help and exit]" + + # options for manipulating directory index + - index + "(--recursive -r)"{--recursive,-r}"[use recursive --add or --unindex]" + "($indexopts)"{--add,-a}"[add specified directories to the index]" + "($indexopts)--unindex[remove specified directories from the index]" + "*:directory:{ (( ${words[(I)-a|--add|--unindex]} )) && _path_files -/ }" + + # define new directory alias + - alias + "--alias=[create alias for this or given directory]:directory-alias:()" + '1:directory:{ (( words[(I)--alias*] )) && _path_files -/ }' + + # remove definition of directory alias + - unalias + "--unalias[remove definition of directory alias]" + "*::directory alias:->scd-alias-target" + + # act on the directory change + - scd + "(--all -A)"{--all,-A}"[include less likely and ignored paths]" + "--list[print matching directories and exit]" + "(--verbose -v)"{--verbose,-v}"[show directory ranking and full paths]" + "(--push -p)"{--push,-p}"[change directory with 'pushd']" + "1::directory alias:->scd-alias-target" + "*:patterns:()" +) + +_arguments -S -C $myargs && ret=0 + + +if [[ "$state" == scd-alias-target && -s ~/.scdalias.zsh ]]; then + local -a scdaliases + scdaliases=( ) + eval "$(setopt extendedglob + phome="(#b)(#s)${HOME}(/*)#(#e)" + builtin hash -dr + source ~/.scdalias.zsh && + for k v in ${(kv)nameddirs}; do + scdaliases+=( $k:${v/${~phome}/"~"${match[1]}} ) + done + complete_unalias=${+opt_args[unalias---unalias]} + if (( complete_unalias && ! ${+nameddirs[OLD]} )); then + scdaliases+=( 'OLD:all aliases to non-existent paths' ) + fi + typeset -p scdaliases )" + _describe -t scdaliases scdalias scdaliases +fi diff --git a/plugins/scd/scd b/plugins/scd/scd old mode 100644 new mode 100755 index 39b28237d..a7db6c265 --- a/plugins/scd/scd +++ b/plugins/scd/scd @@ -1,29 +1,39 @@ #!/bin/zsh -f emulate -L zsh + +local RUNNING_AS_COMMAND= local EXIT=return if [[ $(whence -w $0) == *:' 'command ]]; then - emulate -R zsh - local RUNNING_AS_COMMAND=1 + RUNNING_AS_COMMAND=1 EXIT=exit fi local DOC='scd -- smart change to a recently used directory usage: scd [options] [pattern1 pattern2 ...] -Go to a directory path that contains all fixed string patterns. Prefer -recent or frequently visited directories as found in the directory index. +Go to a directory path that matches all patterns. Prefer recent or +frequently visited directories as found in the directory index. Display a selection menu in case of multiple matches. +Special patterns: + ^PAT match at the path root, "^/home" + PAT$ match paths ending with PAT, "man$" + ./ match paths under the current directory + :PAT require PAT to span the tail, ":doc", ":re/doc" + Options: - -a, --add add specified directories to the directory index. + -a, --add add current or specified directories to the index. --unindex remove current or specified directories from the index. -r, --recursive apply options --add or --unindex recursively. --alias=ALIAS create alias for the current or specified directory and store it in ~/.scdalias.zsh. --unalias remove ALIAS definition for the current or specified directory from ~/.scdalias.zsh. - -A, --all include all matching directories. Disregard matching by - directory alias and filtering of less likely paths. + Use "OLD" to purge aliases to non-existent directories. + -A, --all display all directories even those excluded by patterns + in ~/.scdignore. Disregard unique match for a directory + alias and filtering of less likely paths. + -p, --push use "pushd" to change to the target directory. --list show matching directories and exit. -v, --verbose display directory rank in the selection menu. -h, --help display this message and exit. @@ -36,18 +46,28 @@ local SCD_MEANLIFE=${SCD_MEANLIFE:-86400} local SCD_THRESHOLD=${SCD_THRESHOLD:-0.005} local SCD_SCRIPT=${RUNNING_AS_COMMAND:+$SCD_SCRIPT} local SCD_ALIAS=~/.scdalias.zsh +local SCD_IGNORE=~/.scdignore -local ICASE a d m p i maxrank threshold +# Minimum logarithm of probability. Avoids out of range warning in exp(). +local -r MINLOGPROB=-15 + +# When false, use case-insensitive globbing to fix PWD capitalization. +local PWDCASECORRECT=true +if [[ ${OSTYPE} == darwin* ]]; then + PWDCASECORRECT=false +fi + +local a d m p i maxrank threshold local opt_help opt_add opt_unindex opt_recursive opt_verbose -local opt_alias opt_unalias opt_all opt_list -local -A drank dalias +local opt_alias opt_unalias opt_all opt_push opt_list +local -A drank dalias scdignore local dmatching local last_directory -setopt extendedhistory extendedglob noautonamedirs brace_ccl +setopt extendedglob noautonamedirs brace_ccl -# If SCD_SCRIPT is defined make sure the file exists and is empty. -# This removes any previous old commands. +# If SCD_SCRIPT is defined make sure that that file exists and is empty. +# This removes any old previous commands from the SCD_SCRIPT file. [[ -n "$SCD_SCRIPT" ]] && [[ -s $SCD_SCRIPT || ! -f $SCD_SCRIPT ]] && ( umask 077 : >| $SCD_SCRIPT @@ -56,13 +76,17 @@ setopt extendedhistory extendedglob noautonamedirs brace_ccl # process command line options zmodload -i zsh/zutil zmodload -i zsh/datetime -zparseopts -D -- a=opt_add -add=opt_add -unindex=opt_unindex \ +zmodload -i zsh/parameter +zparseopts -D -E -- a=opt_add -add=opt_add -unindex=opt_unindex \ r=opt_recursive -recursive=opt_recursive \ -alias:=opt_alias -unalias=opt_unalias \ - A=opt_all -all=opt_all -list=opt_list \ + A=opt_all -all=opt_all p=opt_push -push=opt_push -list=opt_list \ v=opt_verbose -verbose=opt_verbose h=opt_help -help=opt_help \ || $EXIT $? +# remove the first instance of "--" from positional arguments +argv[(i)--]=( ) + if [[ -n $opt_help ]]; then print $DOC $EXIT @@ -71,6 +95,22 @@ fi # load directory aliases if they exist [[ -r $SCD_ALIAS ]] && source $SCD_ALIAS +# load scd-ignore patterns if available +if [[ -s $SCD_IGNORE ]]; then + setopt noglob + <$SCD_IGNORE \ + while read p; do + [[ $p != [\#]* ]] || continue + [[ -n $p ]] || continue + # expand leading tilde if it has valid expansion + if [[ $p == [~]* ]] && ( : ${~p} ) 2>/dev/null; then + p=${~p} + fi + scdignore[$p]=1 + done + setopt glob +fi + # Private internal functions are prefixed with _scd_Y19oug_. # Clean them up when the scd function returns. setopt localtraps @@ -79,9 +119,17 @@ trap 'unfunction -m "_scd_Y19oug_*"' EXIT # works faster than the (:a) modifier and is compatible with zsh 4.2.6 _scd_Y19oug_abspath() { set -A $1 ${(ps:\0:)"$( - unfunction -m "*"; shift + setopt pushdsilent + unfunction -m "*" + unalias -m "*" + unset CDPATH + shift for d; do - cd $d && print -Nr -- $PWD && cd $OLDPWD + pushd $d || continue + $PWDCASECORRECT && + print -Nr -- $PWD || + print -Nr -- (#i)$PWD + popd 2>/dev/null done )"} } @@ -106,47 +154,76 @@ if [[ -n $opt_alias ]]; then $EXIT $? fi -# undefine directory alias +# undefine one or more directory aliases if [[ -n $opt_unalias ]]; then - if [[ -n $1 && ! -d $1 ]]; then - print -u2 "'$1' is not a directory." - $EXIT 1 - fi - _scd_Y19oug_abspath a ${1:-$PWD} - a=$(print -rD ${a}) - if [[ $a != [~][^/]## ]]; then - $EXIT + local -U uu + local ec=0 + uu=( ${*:-${PWD}} ) + if (( ${uu[(I)OLD]} && ${+nameddirs[OLD]} == 0 )); then + uu=( ${uu:#OLD} ${(ps:\0:)"$( + hash -dr + if [[ -r $SCD_ALIAS ]]; then + source $SCD_ALIAS + fi + for a d in ${(kv)nameddirs}; do + [[ -d $d ]] || print -Nr -- $a + done + )"} + ) fi - a=${a#[~]} - # unalias in the current shell, update alias file if successful - if unhash -d -- $a 2>/dev/null && [[ -r $SCD_ALIAS ]]; then + m=( ) + for p in $uu; do + d=$p + if [[ ${+nameddirs[$d]} == 0 && -d $d ]]; then + _scd_Y19oug_abspath d $d + fi + a=${(k)nameddirs[$d]:-${(k)nameddirs[(r)$d]}} + if [[ -z $a ]]; then + ec=1 + print -u2 "'$p' is neither a directory alias nor an aliased path." + continue + fi + # unalias in the current shell and remember to update the alias file + if unhash -d -- $a 2>/dev/null; then + m+=( $a ) + fi + done + if [[ $#m != 0 && -r $SCD_ALIAS ]]; then ( umask 077 hash -dr source $SCD_ALIAS - unhash -d -- $a 2>/dev/null && + for a in $m; do + unhash -d -- $a 2>/dev/null + done hash -dL >| $SCD_ALIAS - ) + ) || ec=$? fi - $EXIT $? + $EXIT $ec fi -# The "compress" function collapses repeated directories to -# one entry with a time stamp that gives equivalent-probability. +# The "compress" function collapses repeated directories into +# a single entry with a time-stamp yielding an equivalent probability. _scd_Y19oug_compress() { - awk -v epochseconds=$EPOCHSECONDS -v meanlife=$SCD_MEANLIFE ' - BEGIN { FS = "[:;]"; } - length($0) < 4096 && $2 > 0 { + awk -v epochseconds=$EPOCHSECONDS \ + -v meanlife=$SCD_MEANLIFE \ + -v minlogprob=$MINLOGPROB \ + ' + BEGIN { + FS = "[:;]"; + pmin = exp(minlogprob); + } + /^: deleted:0;/ { next; } + length($0) < 4096 && $2 > 1000 { + df = $0; + sub("^[^;]*;", "", df); + if (!df) next; tau = 1.0 * ($2 - epochseconds) / meanlife; - if (tau < -6.9078) tau = -6.9078; - prob = exp(tau); - sub(/^[^;]*;/, ""); - if (NF) { - dlist[last[$0]] = ""; - dlist[NR] = $0; - last[$0] = NR; - ptot[$0] += prob; - } + prob = (tau < minlogprob) ? pmin : exp(tau); + dlist[last[df]] = ""; + dlist[NR] = df; + last[df] = NR; + ptot[df] += prob; } END { for (i = 1; i <= NR; ++i) { @@ -157,26 +234,38 @@ _scd_Y19oug_compress() { } } } - ' $* + ' $* } -# Rewrite directory index if it is at least 20% oversized -if [[ -s $SCD_HISTFILE ]] && \ -(( $(wc -l <$SCD_HISTFILE) > 1.2 * $SCD_HISTSIZE )); then - # compress repeated entries - m=( ${(f)"$(_scd_Y19oug_compress $SCD_HISTFILE)"} ) - # purge non-existent directories - m=( ${(f)"$( - for a in $m; do - if [[ -d ${a#*;} ]]; then print -r -- $a; fi - done - )"} - ) - # cut old entries if still oversized - if [[ $#m -gt $SCD_HISTSIZE ]]; then - m=( ${m[-$SCD_HISTSIZE,-1]} ) - fi - print -lr -- $m >| ${SCD_HISTFILE} +# Rewrite directory index if it is at least 20% oversized. +local curhistsize +if [[ -z $opt_unindex && -s $SCD_HISTFILE ]] && \ +curhistsize=$(wc -l <$SCD_HISTFILE) && \ +(( $curhistsize > 1.2 * $SCD_HISTSIZE )); then + # Compress repeated entries in a background process. + ( + m=( ${(f)"$(_scd_Y19oug_compress $SCD_HISTFILE)"} ) + # purge non-existent and ignored directories + m=( ${(f)"$( + for a in $m; do + d=${a#*;} + [[ -z ${scdignore[(k)$d]} ]] || continue + [[ -d $d ]] || continue + $PWDCASECORRECT || d=( (#i)${d} ) + t=${a%%;*} + print -r -- "${t};${d}" + done + )"} + ) + # cut old entries if still oversized + if [[ $#m -gt $SCD_HISTSIZE ]]; then + m=( ${m[-$SCD_HISTSIZE,-1]} ) + fi + # Checking existence of many directories could have taken a while. + # Append any index entries added in meantime. + m+=( ${(f)"$(sed "1,${curhistsize}d" $SCD_HISTFILE)"} ) + print -lr -- $m >| ${SCD_HISTFILE} + ) &| fi # Determine the last recorded directory @@ -197,13 +286,8 @@ _scd_Y19oug_record() { } if [[ -n $opt_add ]]; then - for d; do - if [[ ! -d $d ]]; then - print -u2 "Directory '$d' does not exist." - $EXIT 2 - fi - done - _scd_Y19oug_abspath m ${*:-$PWD} + m=( ${^${argv:-$PWD}}(N-/) ) + _scd_Y19oug_abspath m ${m} _scd_Y19oug_record $m if [[ -n $opt_recursive ]]; then for d in $m; do @@ -220,6 +304,7 @@ if [[ -n $opt_unindex ]]; then if [[ ! -s $SCD_HISTFILE ]]; then $EXIT fi + argv=( ${argv:-$PWD} ) # expand existing directories in the argument list for i in {1..$#}; do if [[ -d ${argv[i]} ]]; then @@ -227,24 +312,28 @@ if [[ -n $opt_unindex ]]; then argv[i]=${d} fi done + # strip trailing slashes, but preserve the root path + argv=( ${argv/(#m)?\/##(#e)/${MATCH[1]}} ) m="$(awk -v recursive=${opt_recursive} ' BEGIN { for (i = 2; i < ARGC; ++i) { argset[ARGV[i]] = 1; delete ARGV[i]; } + unindex_root = ("/" in argset); } 1 { d = $0; sub(/^[^;]*;/, "", d); if (d in argset) next; } recursive { + if (unindex_root) exit; for (a in argset) { if (substr(d, 1, length(a) + 1) == a"/") next; } } { print $0 } - ' $SCD_HISTFILE ${*:-$PWD} )" || $EXIT $? + ' $SCD_HISTFILE $* )" || $EXIT $? : >| ${SCD_HISTFILE} [[ ${#m} == 0 ]] || print -r -- $m >> ${SCD_HISTFILE} $EXIT @@ -252,67 +341,113 @@ fi # The "action" function is called when there is just one target directory. _scd_Y19oug_action() { - cd $1 || return $? + local cdcmd=cd + [[ -z ${opt_push} ]] || cdcmd=pushd + builtin $cdcmd $1 || return $? if [[ -z $SCD_SCRIPT && -n $RUNNING_AS_COMMAND ]]; then print -u2 "Warning: running as command with SCD_SCRIPT undefined." fi if [[ -n $SCD_SCRIPT ]]; then - print -r "cd ${(q)1}" >| $SCD_SCRIPT + local d=$1 + if [[ $OSTYPE == cygwin && ${(L)SCD_SCRIPT} == *.bat ]]; then + d=$(cygpath -aw .) + fi + print -r "${cdcmd} ${(qqq)d}" >| $SCD_SCRIPT fi } -# Match and rank patterns to the index file -# set global arrays dmatching and drank +# Select and order indexed directories by matching command-line patterns. +# Set global arrays dmatching and drank. _scd_Y19oug_match() { ## single argument that is an existing directory or directory alias if [[ -z $opt_all && $# == 1 ]] && \ - [[ -d ${d::=$1} || -d ${d::=${nameddirs[$1]}} ]] && [[ -x $d ]]; + [[ -d ${d::=${nameddirs[$1]}} || -d ${d::=$1} ]] && [[ -x $d ]]; then _scd_Y19oug_abspath dmatching $d drank[${dmatching[1]}]=1 return fi - # ignore case unless there is an argument with an uppercase letter - [[ "$*" == *[[:upper:]]* ]] || ICASE='(#i)' - # support "$" as an anchor for the directory name ending + # quote brackets when PWD is /Volumes/[C]/ + local qpwd=${PWD//(#m)[][]/\\${MATCH}} + + # support "./" as an alias for $PWD to match only subdirectories. + argv=( ${argv/(#s).\/(#e)/(#s)${qpwd}(|/*)(#e)} ) + + # support "./pat" as an alias for $PWD/pat. + argv=( ${argv/(#m)(#s).\/?*/(#s)${qpwd}${MATCH#.}} ) + + # support "^" as an anchor for the root directory, e.g., "^$HOME". + argv=( ${argv/(#m)(#s)\^?*/(#s)${${~MATCH[2,-1]}}} ) + + # support "$" as an anchor at the end of directory name. argv=( ${argv/(#m)?[$](#e)/${MATCH[1]}(#e)} ) - # calculate rank of all directories in the SCD_HISTFILE and keep it as drank - # include a dummy entry for splitting of an empty string is buggy + # support prefix ":" to match over the tail component. + argv=( ${argv/(#m)(#s):?*/${MATCH[2,-1]}[^/]#(#e)} ) + + # calculate rank of all directories in SCD_HISTFILE and store it in drank. + # include a dummy entry to avoid issues with splitting an empty string. [[ -s $SCD_HISTFILE ]] && drank=( ${(f)"$( print -l /dev/null -10 <$SCD_HISTFILE \ - awk -v epochseconds=$EPOCHSECONDS -v meanlife=$SCD_MEANLIFE ' - BEGIN { FS = "[:;]"; } + awk -v epochseconds=$EPOCHSECONDS \ + -v meanlife=$SCD_MEANLIFE \ + -v minlogprob=$MINLOGPROB \ + ' + BEGIN { + FS = "[:;]"; + pmin = exp(minlogprob); + } + /^: deleted:0;/ { + df = $0; + sub("^[^;]*;", "", df); + delete ptot[df]; + next; + } length($0) < 4096 && $2 > 0 { + df = $0; + sub("^[^;]*;", "", df); + if (!df) next; + dp = df; + while (!(dp in ptot)) { + ptot[dp] = pmin; + sub("//*[^/]*$", "", dp); + if (!dp) break; + } + if ($2 <= 1000) next; tau = 1.0 * ($2 - epochseconds) / meanlife; - if (tau < -6.9078) tau = -6.9078; - prob = exp(tau); - sub(/^[^;]*;/, ""); - if (NF) ptot[$0] += prob; + prob = (tau < minlogprob) ? pmin : exp(tau); + ptot[df] += prob; } - END { for (di in ptot) { print di; print ptot[di]; } }' + END { for (di in ptot) { print di; print ptot[di]; } } + ' )"} ) unset "drank[/dev/null]" # filter drank to the entries that match all arguments for a; do - p=${ICASE}"*(${a})*" + p="(#l)*(${a})*" drank=( ${(kv)drank[(I)${~p}]} ) done - # require at least one argument matches the directory name - p=${ICASE}"*(${(j:|:)argv})[^/]#" + # require that at least one argument matches in directory tail name. + p="(#l)*(${(j:|:)argv})[^/]#" drank=( ${(kv)drank[(I)${~p}]} ) + # discard ignored directories + if [[ -z ${opt_all} ]]; then + for d in ${(k)drank}; do + [[ -z ${scdignore[(k)$d]} ]] || unset "drank[$d]" + done + fi + # build a list of matching directories reverse-sorted by their probabilities dmatching=( ${(f)"$( - for d p in ${(kv)drank}; do - print -r -- "$p $d"; - done | sort -grk1 | cut -d ' ' -f 2- - )"} + builtin printf "%s %s\n" ${(Oakv)drank} | + /usr/bin/sort -grk1 )"} ) + dmatching=( ${dmatching#*[[:blank:]]} ) # do not match $HOME or $PWD when run without arguments if [[ $# == 0 ]]; then @@ -320,12 +455,20 @@ _scd_Y19oug_match() { fi # keep at most SCD_MENUSIZE of matching and valid directories + # mark up any deleted entries in the index + local -A isdeleted m=( ) + isdeleted=( ) for d in $dmatching; do [[ ${#m} == $SCD_MENUSIZE ]] && break - [[ -d $d && -x $d ]] && m+=$d + (( ${+isdeleted[$d]} == 0 )) || continue + [[ -d $d ]] || { isdeleted[$d]=1; continue } + [[ -x $d ]] && m+=$d done dmatching=( $m ) + if [[ -n ${isdeleted} ]]; then + print -lr -- ": deleted:0;"${^${(k)isdeleted}} >> $SCD_HISTFILE + fi # find the maximum rank maxrank=0.0 @@ -343,7 +486,7 @@ _scd_Y19oug_match() { _scd_Y19oug_match $* -## process whatever directories that remained +## process matching directories. if [[ ${#dmatching} == 0 ]]; then print -u2 "No matching directory." $EXIT 1 @@ -367,13 +510,13 @@ if [[ -n $opt_list ]]; then $EXIT fi -## process single directory match +## handle a single matching directory here. if [[ ${#dmatching} == 1 ]]; then _scd_Y19oug_action $dmatching $EXIT $? fi -## here we have multiple matches - display selection menu +## Here we have multiple matches. Let's use the selection menu. a=( {a-z} {A-Z} ) a=( ${a[1,${#dmatching}]} ) p=( ) diff --git a/plugins/scd/scd.plugin.zsh b/plugins/scd/scd.plugin.zsh index 0197c53a1..1a6c18654 100644 --- a/plugins/scd/scd.plugin.zsh +++ b/plugins/scd/scd.plugin.zsh @@ -1,19 +1,17 @@ ## The scd script should autoload as a shell function. -autoload scd +autoload -Uz scd ## If the scd function exists, define a change-directory-hook function ## to record visited directories in the scd index. if [[ ${+functions[scd]} == 1 ]]; then - scd_chpwd_hook() { scd --add $PWD } - autoload add-zsh-hook - add-zsh-hook chpwd scd_chpwd_hook + chpwd_scd() { scd --add $PWD } + autoload -Uz add-zsh-hook + add-zsh-hook chpwd chpwd_scd fi -## Allow scd usage with unquoted wildcard characters such as "*" or "?". -alias scd='noglob scd' - - ## Load the directory aliases created by scd if any. -if [[ -s ~/.scdalias.zsh ]]; then source ~/.scdalias.zsh; fi +if [[ -s ~/.scdalias.zsh ]]; then + source ~/.scdalias.zsh +fi -- cgit v1.2.3-70-g09d2