Elvish: Support for 'cd -'

Created on 15 Jul 2017  Â·  19Comments  Â·  Source: elves/elvish

In bash, cd - expands to cd $OLDPWD, where $OLDPWD holds the previous working directory. It would be nice to have this in elvish. zsh extends on this by providing cd -N (where N is in integer) to access arbitrary 'directory history' items, which may also be worth adding.

maybe

Most helpful comment

@xofyarg Thanks for the example, but it doesn't track directory changes done via the location mode, or direct assignment to $pwd. See below for my idea of introducing APIs for this.

@myfreeweb sorry for not being clear. My current stance is to not support cd - at all, whether in interactive mode or in scripts. But rather, build an interactive feature and an underlying API. I am generally against the use of magical values that are only recognized by a certain command, but this only applies to the Elvish core; you are always welcome to build customizations however you like.

Fish has a per-session directory history that you can navigation using Alt-Left, Alt-Right, that is something I think we can copy. It is comparable to pushd/popd, but doesn't require manual pushing/popping. We should also add a low-level API for more flexibility (which, among other things, should enable you to implement cd - if you like), and we have several choices here. We can implement the per-session directory history as a builtin functionality, and expose this as a variable, say $edit:dir-history (how to choose a name that disambiguates itself from the global directory history would be an interesting question). Then cd - can be implemented as easily as

fn cd- { cd $edit:dir-history[-1] }

We can also add a hook for directory change (see https://elvish.io/ref/edit.html#hooks for how the current hooks work), say $after-cd. Then we can implement the per-session directory history in elv script, rather as a builtin functionality.

@occivink You use case is better covered by:

pwd=$i {
  # do something
}

Using cd - can do the same thing in simpler case, but has two drawbacks: you need to remember to put cd - in the correct place (which might not be trivial if your script grows), and it cannot be nested -- what if you need to change directory in that # do something? This doesn't work:

cd $i
# ...
cd $j
# ...
cd - # undoes cd $j
cd - # doesn't undo cd $i!

But temporary assignment to pwd can be nested:

pwd=$i {
   # ...
   pwd=$j {
      # ...
   }
}

In general, this pattern of do-something-and-undo-later is almost always better implemented with some kind of scoping construct; compare RAII in C++, and with in Python.

All 19 comments

You could implement something like following:

user:cd-pre-dir = ~
fn cd {
   a = $args
    if (and (eq (count $args) 1) (eq $0 -)) {
        a = [$user:cd-pre-dir]
    }
    user:cd-pre-dir = $pwd
    builtin:cd $@a
}

That's pretty much my current approach. I'm not aware of a way to hook in to the storage and make things persistent and cross session though, but that's probably an uncommon use-case.

Would be nice to also have a directory stack, with pushd/popd. (I'm very surprised there are zero mentions of pushd in this repo, including issues.)

The directory stack should not be cross session or persistent!! Just like OLDPWD.

However it should work with implicit cd ("invoking a directory"). And that always invokes the built-in cd, not a custom fn cd…

It seems that cd - is useful in scripts. I would prefer this to be built as an editor feature instead of a feature of cd.

Oops, typo: s/is useful/is rarely useful/

It would be really weird if cd - worked in interactive mode but not in scripts… I expect all built-in commands to work exactly the same in both

As anecdotal evience, I've used cd - in the past in (fish) scripts in this manner:

for i in (find . -type d) 
    cd $i 
    # do something
    cd -
end

@xofyarg Thanks for the example, but it doesn't track directory changes done via the location mode, or direct assignment to $pwd. See below for my idea of introducing APIs for this.

@myfreeweb sorry for not being clear. My current stance is to not support cd - at all, whether in interactive mode or in scripts. But rather, build an interactive feature and an underlying API. I am generally against the use of magical values that are only recognized by a certain command, but this only applies to the Elvish core; you are always welcome to build customizations however you like.

Fish has a per-session directory history that you can navigation using Alt-Left, Alt-Right, that is something I think we can copy. It is comparable to pushd/popd, but doesn't require manual pushing/popping. We should also add a low-level API for more flexibility (which, among other things, should enable you to implement cd - if you like), and we have several choices here. We can implement the per-session directory history as a builtin functionality, and expose this as a variable, say $edit:dir-history (how to choose a name that disambiguates itself from the global directory history would be an interesting question). Then cd - can be implemented as easily as

fn cd- { cd $edit:dir-history[-1] }

We can also add a hook for directory change (see https://elvish.io/ref/edit.html#hooks for how the current hooks work), say $after-cd. Then we can implement the per-session directory history in elv script, rather as a builtin functionality.

@occivink You use case is better covered by:

pwd=$i {
  # do something
}

Using cd - can do the same thing in simpler case, but has two drawbacks: you need to remember to put cd - in the correct place (which might not be trivial if your script grows), and it cannot be nested -- what if you need to change directory in that # do something? This doesn't work:

cd $i
# ...
cd $j
# ...
cd - # undoes cd $j
cd - # doesn't undo cd $i!

But temporary assignment to pwd can be nested:

pwd=$i {
   # ...
   pwd=$j {
      # ...
   }
}

In general, this pattern of do-something-and-undo-later is almost always better implemented with some kind of scoping construct; compare RAII in C++, and with in Python.

it doesn't track directory changes done via the location mode

My bad, I actually use narrow mode, which calls a closure that does the magic.

... or direct assignment to $pwd

I was surprised to know $pwd is not readonly.

The hooks based implementation sounds like a more general solution to me. It could be leveraged to build session based dir-history.

Fish has a per-session directory history that you can navigation using Alt-Left, Alt-Right, that is something I think we can copy.

ooh not bad

It is comparable to pushd/popd, but doesn't require manual pushing/popping.

zsh has automatic pushd!

We can also add a hook for directory change

That would allow auto pushd to be implemented, yes :)

Hi all,
@xiaq Let's say cd - isn't implemented as a core function as you say but rather left to the user to implement fn cd- { cd $edit:dir-history[-1] }; my question is: what would be the similar way to quickly access the previous working directory in elvish?
When you're rushing throwing commands into a terminal cd - makes a whole lot of a difference.

I agree, cd - is the one thing I'm missing the most from my pre-elvish time.

In case anyone is interested, I've implemented a directory stack using prompt hooks. It's not perfect but it works reasonably well: https://github.com/zzamboni/elvish-modules/blob/master/dir.org

And how I use it: https://github.com/zzamboni/dot_elvish/blob/master/rc.org#directory-and-command-navigation-and-history

In addition to the directory history, its dir:cd function (which I alias to cd) also supports - as an argument to change to the previous directory.

@xiaq

We can also add a hook for directory change (see https://elvish.io/ref/edit.html#hooks for how the current hooks work), say $after-cd. Then we can implement the per-session directory history in elv script, rather as a builtin functionality.

Autojump implements its functionality in fish via hooks, specifically by hooking into $PWD changes. Perhaps variable hooks is a more flexible/generic solution.

I suggest using a separate command instead of an argument of the builtin: cd- instead of cd -. It requires less typing, and don't cause a trouble if the directory is named -.

I suggest using a separate command instead of an argument of the builtin: cd- instead of cd -. It requires less typing, and don't cause a trouble if the directory is named -.

That is true for any of the other magical path expansion chars such as ~, *, etc. In my four decades of using UNIX operating systems I have never seen a directory named - outside of deliberate attempts to create a security hole. If someone really does need to be able to chdir to such a directory name they can do cd ./-.

I agree with @xiaq that having the builtin cd support cd - is a bad idea. Notwithstanding my previous comment. This is a good example of a POSIX 1003.2 shell feature that should not be adopted by Elvish. And the zsh cd -$n form is especially bad. Whether cd - should be supported interactively as a convenience is a slightly different question. I've used cd - interactively for several decades. Nonetheless, I now think that other solutions are preferable and it definitely should not be supported by the builtin cd command.

My current preference is:

  • Implement a per-session directory history, a la Fish
  • Expose an API for the per-session history, so that users who really want this feature can do something like this:
fn cd [d]{
  if (eq $d -) {
    builtin:cd $edit:session-dir-history[-1]
  } else {
    builtin:cd $d
  }
}

Per-session directory history is now tracked in #1043, closing this.

Was this page helpful?
0 / 5 - 0 ratings