New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

add a "mcd" (menu change directory) command #2847

Closed
krader1961 opened this Issue Mar 23, 2016 · 14 comments

Comments

Projects
None yet
7 participants
@krader1961
Contributor

krader1961 commented Mar 23, 2016

I've never liked the prevd/nextd model for navigating my cd history. And the pushd/popd model, while easier to reason about, is quite limiting. So long ago I wrote a mcd (menu change directory) function for ksh (Korn shell). When I switched to zsh a few years ago I ported that function. Now that I'm using fish full-time I decided it was time to write a fish version of mcd. I'm opening this issue to see if enough people think it is useful enough to be included in the standard fish package.

Below is the function. You can put this in ~/.config/fish/functions/mcd.fish to autoload it or put it in another file and explicitly source it. Here is an example of what it looks like:

screenshot 2016-03-22 18 47 19

Notice that this can also effectively replace the dirh command since, if you just press [enter] or [ctrl-D] at the prompt, it does nothing other than show you your recent cd history.

I've removed the first version of the function that was in my original post. It's been replaced by the updated version in the attachment below.

@anordal

This comment has been minimized.

Show comment
Hide comment
@anordal

anordal Mar 23, 2016

Contributor

Looks more fishy than prevd/nextd/histd — all in one command. 👍
This is actually my main problem with prevd/nextd/histd (at least was, in the beginning).

I tried it out — won't go back to prevd/nextd/histd I think. But some comments:

  • A separate issue, but set_color -b brcyan silently failed in Konsole (tried both TERM=xterm and TERM=konsole). Surprising, since Konsole supports 24bit color codes, e.g. printf '%s%s%s\n' \e'[48;2;250;80;0;30m' habanero (set_color normal). In the Linux console (TERM=linux), it failed with "Color not valid in TERM = linux: brcyan". Replacing brcyan with cyan worked in both terminals.
  • With a bright background color, consider setting the foreground color to black (in case it is white, as on a black terminal).
  • There can be duplicates, which are not so useful in a menu. Maybe deduplication makes sense to take care of at a lower level (#2842)?
  • I'm a bit clumsy entering numbers. How about alphabetical numbering? But I would love to navigate such a menu with arrow keys. Alternatively, the Alt+left/right trick should have shown a menu.

I also use the similar "jump" function I mentioned in #1969. They complement each other, as it uses bookmarks instead of history — sometimes, the directory you want is not in your bookmarks, and sometimes, it's not in your history. It differs a bit in user interface too by taking arguments instead of being menu driven (I find that suitable for bookmarks because they are static).

One feature you might consider stealing from "jump" is the ability to edit (at the prompt) the selected item. I think it would make more sense in a menu driven interface.

Contributor

anordal commented Mar 23, 2016

Looks more fishy than prevd/nextd/histd — all in one command. 👍
This is actually my main problem with prevd/nextd/histd (at least was, in the beginning).

I tried it out — won't go back to prevd/nextd/histd I think. But some comments:

  • A separate issue, but set_color -b brcyan silently failed in Konsole (tried both TERM=xterm and TERM=konsole). Surprising, since Konsole supports 24bit color codes, e.g. printf '%s%s%s\n' \e'[48;2;250;80;0;30m' habanero (set_color normal). In the Linux console (TERM=linux), it failed with "Color not valid in TERM = linux: brcyan". Replacing brcyan with cyan worked in both terminals.
  • With a bright background color, consider setting the foreground color to black (in case it is white, as on a black terminal).
  • There can be duplicates, which are not so useful in a menu. Maybe deduplication makes sense to take care of at a lower level (#2842)?
  • I'm a bit clumsy entering numbers. How about alphabetical numbering? But I would love to navigate such a menu with arrow keys. Alternatively, the Alt+left/right trick should have shown a menu.

I also use the similar "jump" function I mentioned in #1969. They complement each other, as it uses bookmarks instead of history — sometimes, the directory you want is not in your bookmarks, and sometimes, it's not in your history. It differs a bit in user interface too by taking arguments instead of being menu driven (I find that suitable for bookmarks because they are static).

One feature you might consider stealing from "jump" is the ability to edit (at the prompt) the selected item. I think it would make more sense in a menu driven interface.

@krader1961

This comment has been minimized.

Show comment
Hide comment
@krader1961

krader1961 Mar 23, 2016

Contributor

The set_color failure is probably because you're using fish 2.2.0. The "bright" variants were introduced by me last November in commit 0a0acc8. I've changed the code to use existing fish_color_* vars. Also, if $PWD appears in the history it is now highlighted the same way the dirh command does it.

In the new version below I've de-duped the history and added the ability to select by letter or number. I've also changed the logic so the most recently visited directory is always selected by entering "a" or "1" rather than it being an essentially random number that depends on the state of the cd history. This also means you can get the effect of prevd 2 by running mcd and always pressing "b" or "2" (if prevd did duplicate elimination that is).

Being able to pick an entry by tabbing through the list or using arrow keys is something we can add when issue #2805 to expose the fish pager as a callable widget is implemented.

mcd.fish.txt

Contributor

krader1961 commented Mar 23, 2016

The set_color failure is probably because you're using fish 2.2.0. The "bright" variants were introduced by me last November in commit 0a0acc8. I've changed the code to use existing fish_color_* vars. Also, if $PWD appears in the history it is now highlighted the same way the dirh command does it.

In the new version below I've de-duped the history and added the ability to select by letter or number. I've also changed the logic so the most recently visited directory is always selected by entering "a" or "1" rather than it being an essentially random number that depends on the state of the cd history. This also means you can get the effect of prevd 2 by running mcd and always pressing "b" or "2" (if prevd did duplicate elimination that is).

Being able to pick an entry by tabbing through the list or using arrow keys is something we can add when issue #2805 to expose the fish pager as a callable widget is implemented.

mcd.fish.txt

@krader1961 krader1961 self-assigned this Mar 23, 2016

@krader1961

This comment has been minimized.

Show comment
Hide comment
@krader1961

krader1961 Mar 24, 2016

Contributor

Another tweak: Don't assume a given fish_color_* var exists.

mcd.fish.txt

Contributor

krader1961 commented Mar 24, 2016

Another tweak: Don't assume a given fish_color_* var exists.

mcd.fish.txt

@anordal

This comment has been minimized.

Show comment
Hide comment
@anordal

anordal Mar 24, 2016

Contributor

You're a mind reader, that's more than I asked for. Perfect!

Regarding brcyan, that color doesn't work even as of your "limit size of cd history to 25 directories" — a commit that evidently works.

Contributor

anordal commented Mar 24, 2016

You're a mind reader, that's more than I asked for. Perfect!

Regarding brcyan, that color doesn't work even as of your "limit size of cd history to 25 directories" — a commit that evidently works.

@krader1961

This comment has been minimized.

Show comment
Hide comment
@krader1961

krader1961 Mar 24, 2016

Contributor

I can reproduce the set_color issue if I set $TERM to linux or xterm. It works fine if $TERM is xterm-256color. The problem is that those two term types and konsole define the number of supported colors as eight (e.g., infocmp -I linux | grep colors#). Try linux-16color and konsole-256color.

Contributor

krader1961 commented Mar 24, 2016

I can reproduce the set_color issue if I set $TERM to linux or xterm. It works fine if $TERM is xterm-256color. The problem is that those two term types and konsole define the number of supported colors as eight (e.g., infocmp -I linux | grep colors#). Try linux-16color and konsole-256color.

@floam

This comment has been minimized.

Show comment
Hide comment
@floam

floam Apr 2, 2016

Member

It's too bad this won't do anything on a new shell session, especially since iTerm and Terminal.app give me the impression of continuing a sesison with my previous directory restored and there being some scrollback. This will be nice once you can use the pager.

Perhaps you should enhance fish's history builtin to accommodate use cases such as this - I think that's why it is (intended to be) YAML and makes an effort to record some extra data already. I rarely used the directory history built in to fish but see myself using this more. Obviously both could benefit there.

Member

floam commented Apr 2, 2016

It's too bad this won't do anything on a new shell session, especially since iTerm and Terminal.app give me the impression of continuing a sesison with my previous directory restored and there being some scrollback. This will be nice once you can use the pager.

Perhaps you should enhance fish's history builtin to accommodate use cases such as this - I think that's why it is (intended to be) YAML and makes an effort to record some extra data already. I rarely used the directory history built in to fish but see myself using this more. Obviously both could benefit there.

@anordal

This comment has been minimized.

Show comment
Hide comment
@anordal

anordal Apr 2, 2016

Contributor

too bad this won't do anything on a new shell session

Have you considered bookmarks?

Contributor

anordal commented Apr 2, 2016

too bad this won't do anything on a new shell session

Have you considered bookmarks?

@jrobeson

This comment has been minimized.

Show comment
Hide comment
@jrobeson

jrobeson Apr 25, 2016

@krader1961 : can you put this mcd command in a gist, that way it's easier to stay up to date with it.

jrobeson commented Apr 25, 2016

@krader1961 : can you put this mcd command in a gist, that way it's easier to stay up to date with it.

@s4code

This comment has been minimized.

Show comment
Hide comment
@s4code

s4code Apr 29, 2016

I’ve implemented an alternative solution to that problem. I called that command "go-back”. It uses complete to generate menu. go-back is not intended to be used directly, I mean that you should bind it to a key. go-back uses leading numbers to sort completions in the right order.

fish-go-back

When it's called with an argument, it strips the leading number from this string and passes the result to cd command. Otherwise it prints the list of visited directories.

function go-back --description "Prints the visited directories"
    if count $argv > /dev/null
        set -l string (type -t string ^ /dev/null)
        if test "$string" = builtin
            cd (string replace -r '^\d+:' '' -- $argv[1])
        else
            cd (printf "%s\n" $argv[1] | sed -r 's/^[0-9]+://')
        end
        return
    end

    set -l alldirs $dirprev $dirnext
    set -l dirhist
    for dir in $alldirs[-1..1]
        if test -d "$dir" -a ! \( $dir = $PWD \)
            if not contains -- $dir $dirhist
                set dirhist $dirhist $dir
                echo (count $dirhist):$dir
            end
        end
    end
end

complete -c go-back -x -a "(go-back)"

And finally the function that should be bound to a key. When that function is called first time it replaces the command line with the string “ go-back “ and calls complete, but only when the command line is empty. Note the leading space, go-back doesn’t pollute your history. When it’s called second time it acts like an “Enter” key.

function __fish_go-back
    if commandline --search-mode
        return
    end

    set -l cmd (commandline -po)
    if count $cmd > /dev/null
        if test "$cmd[1]" = "go-back"
            commandline -f execute
            if commandline --paging-mode
                commandline -f execute
            end
        end
        return
    end

    set -l dirhist (go-back)
    if test -n "$dirhist"
        commandline -r " go-back "
        commandline -f complete down-line
        return
    end

    printf "<directory history is empty>"
    printf "\n%.0s" (fish_prompt)
    commandline -f repaint
end

bind \eh '__fish_go-back'

I bound this function to "Alt+h" (my keybindings are very different from defaults). So when I need to go back I just hold down the “Alt” key and press “h” twice.

What do you guys think about this?

P.S.
I can’t find a better place for this code than my fish_user_key_bindings.fish :)
Here is a gist if someone needs it.

s4code commented Apr 29, 2016

I’ve implemented an alternative solution to that problem. I called that command "go-back”. It uses complete to generate menu. go-back is not intended to be used directly, I mean that you should bind it to a key. go-back uses leading numbers to sort completions in the right order.

fish-go-back

When it's called with an argument, it strips the leading number from this string and passes the result to cd command. Otherwise it prints the list of visited directories.

function go-back --description "Prints the visited directories"
    if count $argv > /dev/null
        set -l string (type -t string ^ /dev/null)
        if test "$string" = builtin
            cd (string replace -r '^\d+:' '' -- $argv[1])
        else
            cd (printf "%s\n" $argv[1] | sed -r 's/^[0-9]+://')
        end
        return
    end

    set -l alldirs $dirprev $dirnext
    set -l dirhist
    for dir in $alldirs[-1..1]
        if test -d "$dir" -a ! \( $dir = $PWD \)
            if not contains -- $dir $dirhist
                set dirhist $dirhist $dir
                echo (count $dirhist):$dir
            end
        end
    end
end

complete -c go-back -x -a "(go-back)"

And finally the function that should be bound to a key. When that function is called first time it replaces the command line with the string “ go-back “ and calls complete, but only when the command line is empty. Note the leading space, go-back doesn’t pollute your history. When it’s called second time it acts like an “Enter” key.

function __fish_go-back
    if commandline --search-mode
        return
    end

    set -l cmd (commandline -po)
    if count $cmd > /dev/null
        if test "$cmd[1]" = "go-back"
            commandline -f execute
            if commandline --paging-mode
                commandline -f execute
            end
        end
        return
    end

    set -l dirhist (go-back)
    if test -n "$dirhist"
        commandline -r " go-back "
        commandline -f complete down-line
        return
    end

    printf "<directory history is empty>"
    printf "\n%.0s" (fish_prompt)
    commandline -f repaint
end

bind \eh '__fish_go-back'

I bound this function to "Alt+h" (my keybindings are very different from defaults). So when I need to go back I just hold down the “Alt” key and press “h” twice.

What do you guys think about this?

P.S.
I can’t find a better place for this code than my fish_user_key_bindings.fish :)
Here is a gist if someone needs it.

@floam

This comment has been minimized.

Show comment
Hide comment
@floam

floam Sep 7, 2016

Member

Very cool @s4code - just happened upon this.

Member

floam commented Sep 7, 2016

Very cool @s4code - just happened upon this.

@krader1961 krader1961 modified the milestone: fish-tank Nov 17, 2016

@krader1961 krader1961 added the RFC label Nov 17, 2016

@nhooyr nhooyr referenced this issue Jan 5, 2017

Closed

make cd use pushd #3697

2 of 2 tasks complete
@mouchtaris

This comment has been minimized.

Show comment
Hide comment
@mouchtaris

mouchtaris Jan 16, 2017

@krader1961 could you put this script in a gist, so contributions and updates are easier to track?

mouchtaris commented Jan 16, 2017

@krader1961 could you put this script in a gist, so contributions and updates are easier to track?

@krader1961

This comment has been minimized.

Show comment
Hide comment
@krader1961

krader1961 Jan 17, 2017

Contributor

could you put this script in a gist, so contributions and updates are easier to track?

I could but I would never update it so doing that seems rather pointless. On the other hand my current version is slightly better than what I originally posted. For example, you can now select via letter (with a always taking you to the previous directory):

$ mcd
 c  3)  ~/bittorrent
 b  2)  ~/VMware Machines
 a  1)  ~
Select directory by letter or number:

I think I should just merge my script. It would then be something people automatically get when installing fish and they can open issues to request changes just like they would do for any other aspect of fish. Also, while @s4code's solution is clever I am not a fan of binding this to a key as opposed to making it an explicit command to mimic cd.

Contributor

krader1961 commented Jan 17, 2017

could you put this script in a gist, so contributions and updates are easier to track?

I could but I would never update it so doing that seems rather pointless. On the other hand my current version is slightly better than what I originally posted. For example, you can now select via letter (with a always taking you to the previous directory):

$ mcd
 c  3)  ~/bittorrent
 b  2)  ~/VMware Machines
 a  1)  ~
Select directory by letter or number:

I think I should just merge my script. It would then be something people automatically get when installing fish and they can open issues to request changes just like they would do for any other aspect of fish. Also, while @s4code's solution is clever I am not a fan of binding this to a key as opposed to making it an explicit command to mimic cd.

@pgan002

This comment has been minimized.

Show comment
Hide comment
@pgan002

pgan002 May 27, 2017

Modality in interfaces is problematic in general. One way to avoid modality is to select the index using a modifier key. Unfortunately, pressing ^1 generates the character as 1.

Another way is substring filtering: the user types a string and Fish shows only path names having that string as a substring or the beginning of a sub-path. The user would not have to read or think about the index number of the desired path name, only the name of the directory. This is not currently supported by Fish's menu completion, which is modal, but I think it would be a good idea.

Ideally, we would integrate this previous paths interaction together with selecting a current subdirectory, as an extension of the menu for the cd function. That would make the interaction more general and more discoverable. Example:

> cd ~/some/long/directory
> cd ~/another/long/directory
> cd ~/some/other/directory
> cd ~
> cd <Tab><Tab>
another    bar    foo    mydir
another/long/directory
some/long/directory
some/other/directory
> cd <Down><Down><Down><Down>
another    bar    foo    mydir
another/long/directory
some/long/directory
[some/other/directory]

<Enter>

> cd some/other/directory

This requires more strokes than index selection on average, but probably less cognitive overhead, because of (1) no index numbers and (2) always using the same command for changing directory. If Fish supported menu filtering, the user could do:

> cd <Tab>
another    bar    foo    mydir
another/long/directory
some/long/directory
some/other/directory
> cd <Tab>o
some/[o]ther/directory

<Right>

> cd some/other/directory

Here, Fish filtered matching subpaths, not substrings.

pgan002 commented May 27, 2017

Modality in interfaces is problematic in general. One way to avoid modality is to select the index using a modifier key. Unfortunately, pressing ^1 generates the character as 1.

Another way is substring filtering: the user types a string and Fish shows only path names having that string as a substring or the beginning of a sub-path. The user would not have to read or think about the index number of the desired path name, only the name of the directory. This is not currently supported by Fish's menu completion, which is modal, but I think it would be a good idea.

Ideally, we would integrate this previous paths interaction together with selecting a current subdirectory, as an extension of the menu for the cd function. That would make the interaction more general and more discoverable. Example:

> cd ~/some/long/directory
> cd ~/another/long/directory
> cd ~/some/other/directory
> cd ~
> cd <Tab><Tab>
another    bar    foo    mydir
another/long/directory
some/long/directory
some/other/directory
> cd <Down><Down><Down><Down>
another    bar    foo    mydir
another/long/directory
some/long/directory
[some/other/directory]

<Enter>

> cd some/other/directory

This requires more strokes than index selection on average, but probably less cognitive overhead, because of (1) no index numbers and (2) always using the same command for changing directory. If Fish supported menu filtering, the user could do:

> cd <Tab>
another    bar    foo    mydir
another/long/directory
some/long/directory
some/other/directory
> cd <Tab>o
some/[o]ther/directory

<Right>

> cd some/other/directory

Here, Fish filtered matching subpaths, not substrings.

@krader1961 krader1961 added this to the fish-future milestone Jun 21, 2017

@krader1961

This comment has been minimized.

Show comment
Hide comment
@krader1961

krader1961 Jul 5, 2017

Contributor

@pgan002 makes a good point. We can do that by adding a completion script for the mcd command. We can't modify the completion behavior for cd because doing so isn't backward compatible and too likely to annoy existing fish users. But we can implement both modal and modeless behavior for the mcd command. With modeless behavior achieved via mcd completions that leverage the fish pager. And modal behavior achieved by simply typing mcd and pressing [Enter] with no args. That should have been obvious to me so consider this a face palm. 😄 Thanks for the idea, @pgan002.

Note that I ended up naming the command cdh to avoid conflicts with the mcd command that might be installed on the user's system.

Contributor

krader1961 commented Jul 5, 2017

@pgan002 makes a good point. We can do that by adding a completion script for the mcd command. We can't modify the completion behavior for cd because doing so isn't backward compatible and too likely to annoy existing fish users. But we can implement both modal and modeless behavior for the mcd command. With modeless behavior achieved via mcd completions that leverage the fish pager. And modal behavior achieved by simply typing mcd and pressing [Enter] with no args. That should have been obvious to me so consider this a face palm. 😄 Thanks for the idea, @pgan002.

Note that I ended up naming the command cdh to avoid conflicts with the mcd command that might be installed on the user's system.

@krader1961 krader1961 removed the RFC label Jul 5, 2017

@krader1961 krader1961 added this to the fish 2.7.0 milestone Jul 5, 2017

@krader1961 krader1961 removed this from the fish-future milestone Jul 5, 2017

krader1961 added a commit to krader1961/fish-shell that referenced this issue Jul 5, 2017

@krader1961 krader1961 closed this in 8cc4639 Jul 5, 2017

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment