An option to disable automatic symlink resolution.
Say I'm in ~/projects/myproject/load and inside I have the symlink
library -> ~/other-projects/this-project/library.
If I :cd into library (e.g. to inspect/edit what's inside), instead of staying under ~/projects/... Kakoune moves all the way over to ~/other-projects/....
To go back to where I was I'd have to type the path manually, as opposed to a quicker :cd .. if Kakoune had used the original directory structure.
Just to be clear: library is a directory, and if you run ls -a there you will see that .. is an actual physical on-disk subdirectory stored inside that directory, and it just happens to have the same inode as ~/other-projects/this-project/. When you cd .., you're not removing the last component of the current directory path, you're changing to a new subdirectory, and it takes some exploring to figure out where that new directory exists in the directory tree, if it exists anywhere at all (try making a directory and cding into it in one terminal, then rmdiring that directory from a different terminal: the first terminal winds up in a directory that's adrift from the global directory tree, and a lot of tools get very confused).
bash is one tool that behaves the way you describe, but that's because it adds a bunch of logic to the cd command to test whether a directory is a symlink before changing into it, and maintains its own "current directory" state in parallel with the kernel's. You can turn that behaviour off with set -o physical to follow the physical directory structure, in which case it'll behave the same way Kakoune does.
All that to say, this issue is not "disable automatic symlink resolution" but rather "add logical directory structure tracking". Symlink resolution is a fundamental aspect of how POSIX directories work and can't be disabled, but you can build your own logical abstraction on top that works 99% of the time and is more pleasant for humans.
It's an interesting problem, I would probably handle that in a plugin that doesn't try to resolve paths. Something that always makes sure you can find your way back easily into the "project directory".
@Screwtapello I get it now! Thanks a lot for explaining!
I would probably handle that in a plugin that doesn't try to resolve paths.
I did a thing:
declare-option str cwd
# Use parent shell $PWD
hook -once global ClientCreate .* %{
evaluate-commands -client %val{hook_param} %{
set-option global cwd %val{client_env_PWD}
}
}
unalias global cd
define-command -file-completion -params ..1 cd %{
set-option global cwd %sh{
case "$1" in
".")
echo "$kak_opt_cwd"
;;
"..")
dirname "$kak_opt_cwd"
;;
*)
cd "$kak_opt_cwd"
realpath -s "${1:-$HOME}"
;;
esac
}
change-directory %opt{cwd}
}
I might make it a plugin :)
Only thing that's missing is getting the right directory when kak is run _inside_ a "symlink". $PWD should work for this, but I couldn't get it to work because Kakoune complains about "no client in context" when using %val{client_env_PWD}.
...Now that I think about it, putting it in a Edit: Used the ClientCreate hook should fix that.ClientCreate hook, it works!
This is looking good, thank you all for your help!
I was thinking that your custom cd command could replace cwd when an absolute path is passed as argument (e.g. it starts with /), or append the directory name to it otherwise. You could end up with cwd looking something like /a/../b/c/../../d, but you might resolve the dots away to keep it short.
That'd work too! I'm gonna be keeping it with realpath though
I want to pass the working directory to something else I'm setting up that doesn't resolve the dots.
The whole reason I made this issue in the first place was because of that too, so it would kinda redundant/defeat the purpose if I had to process the path again haha
Edit: Wait you were talking about resolving the path right after setting it (? sorry I'm kinda sleepy lol). Yeah, I think that might work too. I'm gonna be messing around with that tomorrow. Thanks!
So, doing a thing in the ClientCreate hook means it'll get reset every time a client connects. You may want the hook to also make the server :cd to the target directory just to keep everything in sync.
It's hook -once though. It's only gonna run for the first connected client. This also keeps it close to the default behavior.
I've updated the script so it also works with :e and an alternative to %val{buffile}. For buffile to work correctly on files edited at startup, I need to get the non-dereferenced path of the file. Fortunately I'm already using a wrapper for kak so solving this is just a matter of playing around with environment variables:
#!/bin/sh
export kak_dir_resolve_buffile
for i; do
case "$i" in
-*)
;;
*)
kak_dir_resolve_buffile="$i"
;;
esac
done
kak_dir_resolve_buffile="$PWD/$(realpath --relative-to=. -s "$kak_dir_resolve_buffile")"
exec kak "$@"
and adding an extra line to ClientCreate hook:
set-option buffer buffile %val{client_env_kak_dir_resolve_buffile}
In the end my "plugin" looks like this:
declare-option str cwd
declare-option str buffile
# Use parent shell $PWD
hook -once global ClientCreate .* %{
evaluate-commands -client %val{hook_param} %{
set-option global cwd %val{client_env_PWD}
set-option buffer buffile %val{client_env_kak_dir_resolve_buffile}
}
}
unalias global cd
define-command -file-completion -params ..1 cd %{
set-option global cwd %sh{
case "$1" in
".")
echo "$kak_opt_cwd"
;;
"..")
dirname "$kak_opt_cwd"
;;
*)
cd "$kak_opt_cwd"
realpath -s "${1:-$HOME}"
;;
esac
}
change-directory %opt{cwd}
}
unalias global e
define-command -file-completion -params ..1 e %{
hook -once global BufCreate %sh{realpath "$1"} %sh{
printf 'set-option buffer buffile %s' "$(realpath -s "$1")"
}
edit %arg{1}
}
I'm also gonna be changing modelinefmt to use %opt{buffile}.
I'm really happy with the result, but IMO this is a lot of "manual"/hacky stuff that I think could be handled better by the editor internally. It feels wrong to need a kak wrapper just for this.
A way to work around the kak wrapper would exist if there was a way to get the raw command line arguments (or non-derefenced file paths) from kakrc. Something like %val{raw_cmdline}. However I don't see much use for that apart from this specific case.
With that said, I'm reopening this in case you wanna consider it.
It took me some time to figure it out, but here it is!
https://github.com/SeerLite/resolve-path.kak
As I said before, I'd still like to have it as a builtin feature. I only consider the plugin as a workaround (I had a lot of fun making it though).
I think kakoune should not add this, because virtual paths are a dangerously leaky abstraction. To expand on what @Screwtapello said, here is an example to illustrate:
You have a directory ~/foo, and inside it a symlink bar that points to /mnt/baz. You do cd ~/foo/bar and because of virtual paths, both the prompt and pwd tell you that you're in ~/foo/bar. Moving up with cd .. works as you would expect and takes you to ~/foo.
But then you do ls .. and surprise, it lists the content of /mnt/. Worse yet, you decide to do find .. -type f -delete thinking it will delete the files in ~/foo, but instead it deletes the ones in /mnt/.
With virtual paths, .. means different things for buitins (like cd or pwd) and for other programs. If you have complex symlink hierarchies it gets even more unpredictable.
As an aside, I'm saddened that the fish shell caved in and implemented them, even though it used not to.
Thank you, I can see now why you wouldn't want this functionality in Kakoune. I was just reading the Design doc too, and what you described reminded me of the "Limited smartness" point which I think makes a lot of sense.
I also took a look at how (Neo)Vim does it, and I realized it doesn't really track the current directory the way I thought it did. It only displays the non-dereferenced _path of the file_ that's being edited. cd doesn't work like in a shell at all.
Maybe that bit could be added as an optional feature? Sounds pretty harmless.
Anyway, I think I'll just be using my plugin for this. I understand that dangerous things could happen if I'm not careful (:s) but usually I don't really run commands the way you show in your example (from a subdir and using the parent (..) as argument). The only commands I can see myself using the .. syntax with inside Kakoune are the ones that my plugin replaces anyway.
Most helpful comment
I think kakoune should not add this, because virtual paths are a dangerously leaky abstraction. To expand on what @Screwtapello said, here is an example to illustrate:
You have a directory
~/foo, and inside it a symlinkbarthat points to/mnt/baz. You docd ~/foo/barand because of virtual paths, both the prompt andpwdtell you that you're in~/foo/bar. Moving up withcd ..works as you would expect and takes you to~/foo.But then you do
ls ..and surprise, it lists the content of/mnt/. Worse yet, you decide to dofind .. -type f -deletethinking it will delete the files in~/foo, but instead it deletes the ones in/mnt/.With virtual paths,
..means different things for buitins (likecdorpwd) and for other programs. If you have complex symlink hierarchies it gets even more unpredictable.As an aside, I'm saddened that the fish shell caved in and implemented them, even though it used not to.