elves / elvish

Powerful scripting language & versatile interactive shell
https://elv.sh/
BSD 2-Clause "Simplified" License
5.75k stars 303 forks source link

Support for 'cd -' #438

Closed ALSchwalm closed 4 years ago

ALSchwalm commented 7 years ago

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.

xofyarg commented 7 years ago

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
}
ALSchwalm commented 7 years ago

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.

valpackett commented 7 years ago

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

xiaq commented 7 years ago

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.

xiaq commented 7 years ago

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

valpackett commented 7 years ago

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

occivink commented 7 years ago

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
xiaq commented 7 years ago

@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.

xofyarg commented 7 years ago

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.

valpackett commented 7 years ago

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 :)

cig0 commented 7 years ago

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.

muesli commented 7 years ago

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

zzamboni commented 7 years ago

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.

mqudsi commented 7 years ago

@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.

notramo commented 6 years ago

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 -.

krader1961 commented 4 years ago

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 ./-.

krader1961 commented 4 years ago

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.

xiaq commented 4 years ago

My current preference is:

fn cd [d]{
  if (eq $d -) {
    builtin:cd $edit:session-dir-history[-1]
  } else {
    builtin:cd $d
  }
}
xiaq commented 4 years ago

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

omnibs commented 3 years ago

Sorry to write to a closed issue, but can we have an up-to-date snippet?

Absolute beginner here, heard of elvish 30min ago, got it on my nix shell and am trying to do something quick with it. Was hit with this error in @xiaq's script:

compilation error: variable $edit:session-dir-history not found

Elvish 0.15

I also tried the first snippet in the thread and it also failed.

(I could have commented on the other issue but it seemed more general than "how can I cd - in elvish?")

EDIT: Oops, realized xiaq's script was meant as future syntax.

Here's the error on the first script in thread:

compilation error: cannot create variable $user:cd-pre-dir; new variables can only be created in the local scope

I'm guessing that snippet is outdated?

EDIT2: I also tried https://github.com/zzamboni/elvish-modules/blob/master/dir.org

Used it like:

#! /usr/bin/env nix-shell
#! nix-shell -i elvish -p elvish

use epm
epm:install github.com/zzamboni/elvish-modules

use github.com/zzamboni/elvish-modules/dir

use github.com/zzamboni/elvish-modules/alias
alias:new cd "use github.com/zzamboni/elvish-modules/dir; dir:cd"

cd bastion
cd -

Got this error:

Exception: compilation error: variable $edit:current-command not found
/Users/juliano/.elvish/lib/github.com/zzamboni/elvish-modules/dir.elv, line 89:   if (> (count $edit:current-command) 0) {
/Users/juliano/git/NoRedInk/tf-all.elv, line 7: use github.com/zzamboni/elvish-modules/dir
krader1961 commented 3 years ago

@omnibs, Elvish has been evolving rapidly over the past two years. The 0.15.0 release is considered old. In general @zzamboni's modules are only guaranteed to work with Elvish built from a recent commit. Try one of the "HEAD" binaries at https://elv.sh/get/.

P.S., There isn't much need for the alias module anymore since the introduction of the edit:add-var command. What I do is put the following (along with a bunch of other things) in ~/.elvish/lib/interactive.elv:

fn cd [@_args]{
  use github.com/zzamboni/elvish-modules/dir
  dir:cd $@_args
}

Then in ~/.elvish/rc.elv I do this:

use interactive
edit:add-var cd~ $interactive:cd~
omnibs commented 3 years ago

Oh, I see. I got it from nixpkgs where even in unstable we have 0.15.0.

Thanks for clarifying!