Stop Tmux from dereferencing path when creating new window/pane
Asked Answered
K

6

11

When I create new window/pane in tmux, for example via tmux neww or keybindings prefix+c, prefix+% etc, the new pane gets working directory the same as previous pane, but with dereferenced symbolic links in path.

For example, if I am at

/home/user/my-link/a

where my-link -> /mnt/user/, i got to

/mnt/user/a

Explicitly passing new directory to tmux does not work either:

tmux neww -c $(pwd)

Can I disable such dereferencing? I think I can write a workaround via tmux environment variables, but I want a clearer solution.

I am running tmux 1.8 from repos on Ubuntu 14.04.

Kleiman answered 3/10, 2015 at 13:27 Comment(0)
K
4

Summaring all answers and comments from Philipp Wendler and Yacc, I came up with solution that works perfectly for me. I should mension that it uses some tricks on my machine, so they should be used carefully.

It was shown that tmux can't solve problem on it's own, we need some tricks.

First, make that every pane describe it's path in tmux variable TMUX_<pane-id>_PATH. That could be done via aliasing cd or prompt code (I am using that), it doesn't matter:

# get $pane set to pane id without %
tmux set-environment TMUX_"$pane"_PATH $(pwd)

Second, have in path script tmux-neww.sh. It sets NEWW variable to current real path's way. It gets current pane-id as a param:

#!/bin/bash
pane=$(echo "$1" | tr -d '%')
pane_path=$(tmux show-environment TMUX_"$pane"_PATH | sed 's/^[^=]*=//g')
tmux set-environment NEWW "$pane_path"
tmux neww

Third, in tmux.conf:

bind C \
    run "tmux-neww.sh #{pane_id}"

Forth, I have in bash.bashrc test, if shell is being run in tmux. If so, it makes some changes (i.e. adds some variables that my heavy prompt will send some data to tmux variables). Here it tests, if NEWW is set:

neww=$(tmux show-environment NEWW 2> /dev/null | sed 's/^[^=]*=//')
if [ "$neww" != "-NEWW" ] && [ "$neww" != "" ] ; then
    cd "$neww"
fi
tmux set-environment -r NEWW

That may be overwhelming, but works OK.

Kleiman answered 12/10, 2015 at 9:20 Comment(1)
this will be helpful for others, upped it to refund some of your bountyShindig
P
10

This behavior cannot be disabled, and it seems it is not even possible to implement this feature in tmux (source).

In Linux the working directory of a process is always tracked as the actual directory (with symlinks resolved). You can see this by issuing ls -l /proc/self/cwd in the directory /home/user/my-link/a, it will show that the current working directory is actually /mnt/user/a. The reason for this is probably for not running into trouble when the symlink is deleted (or even changed) while a process is in that directory.

The feature that your shell shows you /home/user/my-link/a as working directory is implemented completely in the shell itself. It keeps track of it in the pwd environment variable, but tmux cannot access environment variables of subprocesses.

The easiest way I have found to create a new window as you would like to is

tmux neww "cd $(pwd); exec $SHELL"
Photopia answered 11/10, 2015 at 14:52 Comment(4)
I thought that there were problems in access. Thanks for detailed answer and good solution!Kleiman
And that neww... cannot be bound to a key?Kleiman
I don't think so, it needs to be executed within the shell of the current window, not by tmux itself. You can assign an alias of course to make it shorter.Photopia
Ok. I will try tmux variable hack I used once.Kleiman
S
4

In addition to Philipp's answer, there's a way to work around the runtime substitution problem. You just need to take care that every time you change the directory the global tmux variable PWD is updated with $PWD. In your .bashrc:

mycd() {
  \cd "$@"
  [ -n "$TMUX" ] && tmux set-environment -g PWD $PWD
}
alias cd=mycd

In your .tmux.conf:

bind-key C-n new-window
Shindig answered 12/10, 2015 at 4:25 Comment(6)
This has the problem that the value always points to the last directory across all windows one cd'ed into, so in the new window you might end up in a different directory than the current window if you have multiple windows open.Photopia
It will also work without the -g switch. Could you confirm?Shindig
That's actually the way I am already using (i.e. "I think I can write a workaround via tmux environment variables, but I want a clearer solution.").Kleiman
Oh, no, it's actually shorter because I missed PWD variable in tmux. Beautiful!Kleiman
@PhilippWendler it will work almost perfect, if you set PWD in prompt code. Because I already have heavy prompt one assignment there will not be too much. What I think to solve all problems is actually to rebind neww to 1. assing some var $PWD of that pane 2. do newwKleiman
@PhilippWendler I added new answer that actually works and combines your ideas. Thanks for helping!Kleiman
K
4

Summaring all answers and comments from Philipp Wendler and Yacc, I came up with solution that works perfectly for me. I should mension that it uses some tricks on my machine, so they should be used carefully.

It was shown that tmux can't solve problem on it's own, we need some tricks.

First, make that every pane describe it's path in tmux variable TMUX_<pane-id>_PATH. That could be done via aliasing cd or prompt code (I am using that), it doesn't matter:

# get $pane set to pane id without %
tmux set-environment TMUX_"$pane"_PATH $(pwd)

Second, have in path script tmux-neww.sh. It sets NEWW variable to current real path's way. It gets current pane-id as a param:

#!/bin/bash
pane=$(echo "$1" | tr -d '%')
pane_path=$(tmux show-environment TMUX_"$pane"_PATH | sed 's/^[^=]*=//g')
tmux set-environment NEWW "$pane_path"
tmux neww

Third, in tmux.conf:

bind C \
    run "tmux-neww.sh #{pane_id}"

Forth, I have in bash.bashrc test, if shell is being run in tmux. If so, it makes some changes (i.e. adds some variables that my heavy prompt will send some data to tmux variables). Here it tests, if NEWW is set:

neww=$(tmux show-environment NEWW 2> /dev/null | sed 's/^[^=]*=//')
if [ "$neww" != "-NEWW" ] && [ "$neww" != "" ] ; then
    cd "$neww"
fi
tmux set-environment -r NEWW

That may be overwhelming, but works OK.

Kleiman answered 12/10, 2015 at 9:20 Comment(1)
this will be helpful for others, upped it to refund some of your bountyShindig
S
1

For fish shell users.

Implementing the idea from the answer above by @yacc + comment by @lapshin-dmitry.

Step1

Update fish_prompt.fish so that it updates PWD variable in Tmux

# ~/.config/fish/functions/fish_prompt.fish

function update_tmux_pwd
    if test -n "$TMUX"
        tmux set-environment -g PWD $PWD
    end
end

function fish_prompt --description 'Write out the prompt'
   # ... original body of the function
   update_tmux_pwd
end

Step 2

Check if fish shell actually updates Tmux variable PWD:

~> tmux show-environment -g PWD
PWD=/home/username
~> cd some_directory
some_directory> tmux show-environment -g PWD
PWD=some_directory

Step3

rebind keys that create new pane or window in .tmux.conf:

# ~/.tmux.conf

bind '"' split-window -c "#{PWD}"
bind % split-window -h -c "#{PWD}"
bind c new-window -c "#{PWD}"

Starting tmux session in a specified directory

For the sake of completeness, how to modify a tmux alias, which starts tmux session in a specified directory, so that symlinks are not dereferenced, applying the original idea from yet another answer above

# ~/.config/fish/alias.fish

alias tts 'tmux attach -t sandbox || tmux new -s sandbox "cd /home/username/git/sandbox; exec $SHELL"'
Shorthand answered 12/4, 2022 at 17:46 Comment(1)
Unfortunately this method doesn't seem to work anymore. Even a simple tmux split-window -c "/linked/path" will actually create a split on the "/original/path". You will need to use tmux split-window "cd /linked/path; exec $SHELL"Ephram
B
1

Based on previous answers I found a way that has fewer disadvantages. I'm working on tmux -V = next-3.4.

tmux new-window -c /some/linked/path itself also derefences things, so even if you manage to get a hold of your current path un-dereferenced it doesnt help yet. For me it only works reliably to do set-environment PWD /some/linked/path \; new -window -c /some/linked/path.

The behavior with PWD is only loosely documented at the very end of the man page, but it does what you want in this case.

tmux set-environment PWD /some/linked/path however is global to the session, so simply doing that in your shell prompt everytime you change the path will mix up things. Because it will use the folder you last hit enter on, not the folder of the current pane.

The workaround for that is to use "#{pane_path}" instead of "#{pane_current_path}" (see tmux man page). This is local to the pane and you can control it via escape sequences in your shell prompt.

For zsh that is

function _send_cwd_for_tmux {
    [[ ! -v TMUX ]] && return
    d=$(print -P '%d')
    printf "\033]7;$d\033\\"
}

and then just silently add that to your chpwd hook

add-zsh-hook chpwd _send_cwd_for_tmux

For reference, here's what I have in my tmux config:

# new windows/panes start in the current pane's directory
# NOTE
# - "#{pane_current_path}" is already with symlinks dereferenced
# - "#{pane_path}" comes from a escape code to inform the terminal of CWD
# - we use that to inform tmux about the symlinked version of CWD
# - this happens in zsh prompts everytime the CWD changes
# - all the -c options also dereference things, unless PWD points to the same place
# NOTE
#   "#{pane_current_path}" is already with symlinks dereferenced, it comes from /proc/self/cwd
#   "#{pane_path}" comes from an escape code to inform the terminal of CWD, unset unless you do it
#   we set that in zsh's prompt everytime, it's local to the terminal, not tmux
#   new-window -c still dereferences things
#   unless PWD points to the same thing
#   (this behavior is only half-documenten in tmux, very end of man page)
bind-key c set-environment -F PWD "#{pane_path}" \; new-window -c "#{pane_path}"
bind-key % set-environment -F PWD "#{pane_path}" \; split-window -h -c "#{pane_path}"
bind-key '"' set-environment -F PWD "#{pane_path}" \; split-window -c "#{pane_path}"
# new window to the right of current window, not appended at the end (as default)
# note: default C is for customize-mode
bind-key C set-environment -F PWD "#{pane_path}" \; new-window -a -c "#{pane_path}"
Burp answered 22/7, 2022 at 15:34 Comment(0)
S
0
  • Presume bind c new-window -c "#{pane_current_path}" in your .tmux.conf
  • Assume you have some symbolic link to synced folders at $HOME/
  • Add . cd2link to the end of your .bashrc/.zshrc
  • Make sure cd2link is in your $PATH
  • Add the following to cd2link
  • chmod +x cd2link
#!/bin/bash
BOX="Library/CloudStorage/Box-Box"
ICLOUD="Library/Mobile Documents/com~apple~CloudDocs"
LINKED=(
  "$HOME/$BOX/Box"
  "$HOME/$ICLOUD/iCloud"
)
for p in ${LINKED[@]}; do
  if [[ $PWD == $p* ]] ; then
   cd "$HOME/$(basename $p)${PWD#$p}"
  fi
done
Solidarity answered 30/8, 2022 at 22:12 Comment(0)

© 2022 - 2024 — McMap. All rights reserved.