diff --git a/CHANGELOG.md b/CHANGELOG.md index 84a4bab91..d542ff657 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -8,6 +8,7 @@ - `string unescape` has been implemented to reverse the effects of `string escape` (#3543). - The history file can now be specified by setting the `FISH_HISTORY` variable (#102). - Read history is now controlled by the `FISH_HISTORY` variable rather than the `--mode-name` flag (#1504). +- Implement a `cdh` (change directory using recent history) command to provide a more friendly alternative to prevd/nextd and pushd/popd (#2847). ## Other significant changes diff --git a/doc_src/cd.txt b/doc_src/cd.txt index a2cb95885..caffa842c 100644 --- a/doc_src/cd.txt +++ b/doc_src/cd.txt @@ -14,7 +14,7 @@ If `DIRECTORY` is a relative path, the paths found in the `CDPATH` environment v Note that the shell will attempt to change directory without requiring `cd` if the name of a directory is provided (starting with `.`, `/` or `~`, or ending with `/`). -Fish also ships a wrapper function around the builtin `cd` that understands `cd -` as changing to the previous directory. See also `prevd`. This wrapper function maintains a history of the 25 most recently visited directories in the `$dirprev` and `$dirnext` global variables. +Fish also ships a wrapper function around the builtin `cd` that understands `cd -` as changing to the previous directory. See also `prevd`. This wrapper function maintains a history of the 25 most recently visited directories in the `$dirprev` and `$dirnext` global variables. If you make those universal variables your `cd` history is shared among all fish instances. \subsection cd-example Examples @@ -25,3 +25,7 @@ cd cd /usr/src/fish-shell # changes the working directory to /usr/src/fish-shell \endfish + +\subsection cd-see-also See Also + +See also the `cdh` command for changing to a recently visited directory. diff --git a/doc_src/cdh.txt b/doc_src/cdh.txt new file mode 100644 index 000000000..7d18acd86 --- /dev/null +++ b/doc_src/cdh.txt @@ -0,0 +1,16 @@ +\section cdh cdh - change to a recently visited directory + +\subsection cdh-synopsis Synopsis +\fish{synopsis} +cdh [ directory ] +\endfish + +\subsection cdh-description Description + +`cdh` with no arguments presents a list of recently visited directories. You can then select one of the entries by letter or number. You can also press @key{tab} to use the completion pager to select an item from the list. If you give it a single argument it is equivalent to `cd directory`. + +Note that the `cd` command limits directory history to the 25 most recently visited directories. The history is stored in the `$dirprev` and `$dirnext` variables which this command manipulates. If you make those universal variables your `cd` history is shared among all fish instances. + +\subsection cdh-see-also See Also + +See also the `prevd` and `pushd` commands which also work with the recent `cd` history and are provided for compatibility with other shells. diff --git a/doc_src/nextd.txt b/doc_src/nextd.txt index e02598a9b..61e01f1ef 100644 --- a/doc_src/nextd.txt +++ b/doc_src/nextd.txt @@ -13,6 +13,8 @@ If the `-l` or `--list` flag is specified, the current directory history is also Note that the `cd` command limits directory history to the 25 most recently visited directories. The history is stored in the `$dirprev` and `$dirnext` variables which this command manipulates. +You may be interested in the `cdh` command which provides a more intuitive way to navigate to recently visited directories. + \subsection nextd-example Example \fish diff --git a/doc_src/popd.txt b/doc_src/popd.txt index 928168352..93dd4da37 100644 --- a/doc_src/popd.txt +++ b/doc_src/popd.txt @@ -9,6 +9,7 @@ popd `popd` removes the top directory from the directory stack and changes the working directory to the new top directory. Use `pushd` to add directories to the stack. +You may be interested in the `cdh` command which provides a more intuitive way to navigate to recently visited directories. \subsection popd-example Example diff --git a/doc_src/prevd.txt b/doc_src/prevd.txt index f1e9e6cb3..191d95890 100644 --- a/doc_src/prevd.txt +++ b/doc_src/prevd.txt @@ -13,6 +13,8 @@ If the `-l` or `--list` flag is specified, the current history is also displayed Note that the `cd` command limits directory history to the 25 most recently visited directories. The history is stored in the `$dirprev` and `$dirnext` variables which this command manipulates. +You may be interested in the `cdh` command which provides a more intuitive way to navigate to recently visited directories. + \subsection prevd-example Example \fish diff --git a/doc_src/pushd.txt b/doc_src/pushd.txt index 8fad3bd71..43d62c6dd 100644 --- a/doc_src/pushd.txt +++ b/doc_src/pushd.txt @@ -17,6 +17,8 @@ Without arguments, it exchanges the top two directories in the stack. See also `dirs` and `dirs -c`. +You may be interested in the `cdh` command which provides a more intuitive way to navigate to recently visited directories. + \subsection pushd-example Example \fish diff --git a/share/completions/cdh.fish b/share/completions/cdh.fish new file mode 100644 index 000000000..f54e2a2d1 --- /dev/null +++ b/share/completions/cdh.fish @@ -0,0 +1,23 @@ +function __fish_cdh_args + set -l all_dirs $dirprev $dirnext + set -l uniq_dirs + + # This next bit of code doesn't do anything useful at the moment since the fish pager always + # sorts, and eliminates duplicate, entries. But we do this to mimic the modal behavor of `cdh` + # and in hope that the fish pager behavior will be changed to preserve the order of entries. + for dir in $all_dirs[-1..1] + if not contains $dir $uniq_dirs + set uniq_dirs $uniq_dirs $dir + end + end + + for dir in $uniq_dirs + set -l home_dir (string match -r "$HOME(/.*|\$)" "$dir") + if set -q home_dir[2] + set dir "~$home_dir[2]" + end + echo $dir + end +end + +complete -c cdh -xa '(__fish_cdh_args)' diff --git a/share/functions/cdh.fish b/share/functions/cdh.fish new file mode 100644 index 000000000..eb6df8e29 --- /dev/null +++ b/share/functions/cdh.fish @@ -0,0 +1,87 @@ +# Provide a menu of the directories recently navigated to and ask the user to +# choose one to make the new current working directory (cwd). + +function cdh --description "Menu based cd command" + # See if we've been invoked with an argument. Presumably from the `cdh` completion script. + # If we have just treat it as `cd` to the specified directory. + if set -q argv[1] + cd $argv + return + end + + if set -q argv[2] + echo (_ "cdh: Expected zero or one arguments") >&2 + return 1 + end + + set -l all_dirs $dirprev $dirnext + if not set -q all_dirs[1] + echo (_ 'No previous directories to select. You have to cd at least once.') >&2 + return 0 + end + + # Reverse the directories so the most recently visited is first in the list. + # Also, eliminate duplicates; i.e., we only want the most recent visit to a + # given directory in the selection list. + set -l uniq_dirs + for dir in $all_dirs[-1..1] + if not contains $dir $uniq_dirs + set uniq_dirs $uniq_dirs $dir + end + end + + set -l letters a b c d e f g h i j k l m n o p q r s t u v w x y z + set -l dirc (count $uniq_dirs) + if test $dirc -gt (count $letters) + set -l msg (_ 'This should not happen. Have you changed the cd function?') + printf "$msg\n" + set -l msg (_ 'There are %s unique dirs in your history but I can only handle %s') + printf "$msg\n" $dirc (count $letters) + return 1 + end + + # Print the recent directories, oldest to newest. Since we previously + # reversed the list, making the newest entry the first item in the array, + # we count down rather than up. + for i in (seq $dirc -1 1) + set -l dir $uniq_dirs[$i] + set -l label_color normal + set -q fish_color_cwd; and set label_color $fish_color_cwd + set -l dir_color_reset (set_color normal) + set -l dir_color + if test "$dir" = "$PWD" + set dir_color (set_color $fish_color_history_current) + end + + set -l home_dir (string match -r "$HOME(/.*|\$)" "$dir") + if set -q home_dir[2] + set dir "~$home_dir[2]" + end + printf '%s %s %2d) %s %s%s%s\n' (set_color $label_color) $letters[$i] \ + $i (set_color normal) $dir_color $dir $dir_color_reset + end + + # Ask the user which directory from their history they want to cd to. + set -l msg (_ 'Select directory by letter or number: ') + read -l -p "echo '$msg'" choice + if test "$choice" = "" + return 0 + else if string match -q -r '^[a-z]$' $choice + # Convert the letter to an index number. + set choice (contains -i $choice $letters) + end + + set -l msg (_ 'Error: expected a number between 1 and %d or letter in that range, got "%s"') + if string match -q -r '^\d+$' $choice + if test $choice -ge 1 -a $choice -le $dirc + cd $uniq_dirs[$choice] + return + else + printf "$msg\n" $dirc $choice + return 1 + end + else + printf "$msg\n" $dirc $choice + return 1 + end +end