skhd icon indicating copy to clipboard operation
skhd copied to clipboard

Using hotkeys to move between tmux panes

Open noib3 opened this issue 4 years ago • 34 comments

I'd like to use the same keys I use to switch focus between windows (alt - up, alt - down etc...) to switch focus between tmux panes.

Now I don't know if this is a): possible at all, and b): something to be handled by skhd, but I'll try to explain what I do now and hopefully someone can put me on the right track.

Switching focus between tmux panes is done by pressing the prefix key, by default ctrl - b, followed by the arrow key (up, down etc...) corresponding with the direction of the window one wants to focus.

Instead of pressing ctrl-b and an arrow key all the time, in my alacritty config file I have set a key binding so that when I press, for example, cmd + alt - up, alacritty sends the code \x02\x1b\x5b\x41, which corresponds to ctrl - b followed by an up arrow.

So to switch between windows I need to press alt - up and to switch between panes I need to press cmd + alt - up. While this is an improvement from the default way, it's still counterintuitive.

Is there a way to implement this?

noib3 avatar Mar 11 '20 01:03 noib3

By windows, you mean tmux windows (tabs)?

So basically, for example when you select pane on the right, and there's no pane there, you want to go to next window?

ppiotrowicz avatar Mar 12 '20 23:03 ppiotrowicz

If yes, you can bind it directly in tmux with something like this:

bind -n M-Left run-shell "if [ $(tmux display-message -p '#{pane_at_left}') -ne 1 ]; then tmux select-pane -L; else tmux select-window -p; fi"
bind -n M-Right run-shell "if [ $(tmux display-message -p '#{pane_at_right}') -ne 1 ]; then tmux select-pane -R; else tmux select-window -n; fi"
  • bind -n M-Left binds to alt - left arrow, without prefix key
  • run-shell "if [ $(tmux display-message -p '#{pane_at_left}') -ne 1 ]; checks if there's a pane on the left
  • then tmux select-pane -L; if yes, then focus it
  • else tmux select-window -p; fi" else go to previous window

ppiotrowicz avatar Mar 12 '20 23:03 ppiotrowicz

No by windows I mean windows of whatever program is open, e.g. switching between my terminal and browser and so on.

That's why it's so tricky, I somehow have to tell skhd to switch windows (between programs) only if the window is not a terminal with an istance of tmux open and a pane open in that direction. So for example consider this situation.

If I'm focusing firefox and I press alt + right I should switch the focus to my terminal like I do now. On the other hand, if I'm focusing alacritty and I press alt + left I should first check if there's an istance of tmux open, and if there is, like in this case, I should check if there's a pane open in that direction (in the example situation I provided there isn't, because the panes are on top of eachother).

Finally, if I'm focusing the top tmux pane and I press alt - down it should switch the focus to the bottom pane, instead of trying to switch the focus to another window (i.e. another program or another instance of the terminal) below the terminal.

noib3 avatar Mar 13 '20 00:03 noib3

Ok, I get it. I see you're using yabai or chunkwm. I think you would need to do something like this in tmux.conf:

bind C-h run-shell "if [ $(tmux display-message -p '#{pane_at_left}') -ne 1 ]; then tmux select-pane -L; else yabai -m window --focus west || true; fi"
bind C-l run-shell "if [ $(tmux display-message -p '#{pane_at_right}') -ne 1 ]; then tmux select-pane -R; else yabai -m window --focus east || true; fi"
bind C-j run-shell "if [ $(tmux display-message -p '#{pane_at_bottom}') -ne 1 ]; then tmux select-pane -D; else yabai -m window --focus north || true; fi"
bind C-k run-shell "if [ $(tmux display-message -p '#{pane_at_top}') -ne 1 ]; then tmux select-pane -U; else yabai -m window --focus south || true; fi"

I used prefix C-hjkl here, but it doesn't matter in the end.

And now, in skhd you would need to bind alt - arrows to something that checks if currently focused window is alacritty or something else, similar to what was done here https://github.com/koekeishiya/yabai/issues/75 if it's alacritty, send skhd -k "alt - arrow" and if it's not, do the yabai -m window --focus <direction>. I think it should work.

ppiotrowicz avatar Mar 13 '20 07:03 ppiotrowicz

similar to what was done here koekeishiya/yabai#75

Don't do that. Do this instead:

cmd - right [
    *           : yabai -m window --focus east
    "Alacritty" : skhd --key "alt - right"
]

It's important that you use different keybindings here, otherwise you end up in an infinite loop.

dominiklohmann avatar Mar 13 '20 07:03 dominiklohmann

Personally, I have vim splits nested in tmux panes nested in yabai managed windows and it leads me to question my sanity

(Easing management of vim splits in tmux is pretty well documented, though)

strazto avatar Mar 14 '20 05:03 strazto

Ok, I get it. I see you're using yabai or chunkwm. I think you would need to do something like this in tmux.conf:

bind C-h run-shell "if [ $(tmux display-message -p '#{pane_at_left}') -ne 1 ]; then tmux select-pane -L; else yabai -m window --focus west || true; fi"
bind C-l run-shell "if [ $(tmux display-message -p '#{pane_at_right}') -ne 1 ]; then tmux select-pane -R; else yabai -m window --focus east || true; fi"
bind C-j run-shell "if [ $(tmux display-message -p '#{pane_at_bottom}') -ne 1 ]; then tmux select-pane -D; else yabai -m window --focus north || true; fi"
bind C-k run-shell "if [ $(tmux display-message -p '#{pane_at_top}') -ne 1 ]; then tmux select-pane -U; else yabai -m window --focus south || true; fi"

I used prefix C-hjkl here, but it doesn't matter in the end.

And now, in skhd you would need to bind alt - arrows to something that checks if currently focused window is alacritty or something else, similar to what was done here koekeishiya/yabai#75 if it's alacritty, send skhd -k "alt - arrow" and if it's not, do the yabai -m window --focus <direction>. I think it should work.

That should work while in a tmux session, but what if I'm not? I'm gonna send an alt - arrow that is not going to be catched by anything.

noib3 avatar Mar 14 '20 11:03 noib3

Set up the keybinding not in your tmux configuration, but rather in your zshrc/bashrc, and have it act differently depending on whether you're in a multiplexer or not.

dominiklohmann avatar Mar 14 '20 11:03 dominiklohmann

Ok I solved it. The solution was a lot simpler than I was thinking. Instead of spreading parts between my alacritty, shell, tmux and skhd config files, everything can be done within skhd. Simply using

alt - left [
    *           : yabai -m window --focus west
    "Alacritty" : if [ $(tmux display-message -p '#{pane_at_left}') -ne 1 ]; then tmux select-pane -L; else yabai -m window --focus west || true; fi
]

works like a charm. Also setting up the keybindings in my zshrc wouldn't work when I'm in vim or any other program that is not a shell.

Thanks everyone!

noib3 avatar Mar 14 '20 11:03 noib3

I'm reopening this issue because I've come across a problem with the solution I had which I've been trying to solve for the last few hours, clearly without any success.

The problem is that (using the alt - down code here for example)

if [ $(tmux display-message -p '#{pane_at_bottom}') -ne 1 ]; then tmux select-pane -D; else yabai -m window --focus south || true; fi

only checks if the pane currently focused in the tmux session is at the bottom, but doesn't check if the alacritty window has a tmux session running inside it. So consider this situation:

2020:03:20@_01 40 14

the top window is not running any tmux sessions, while the bottom one has a session with two panes, one below the other.

I'm focusing the top window, and the last focused pane in the bottom window is the top one. If I now press alt - down, then

tmux display-message -p '#{pane_at_bottom}'

is going to return 0, and instead of switching windows I switch panes, while still focusing the top window. Now the focused pane is the bottom one so tmux display.... is going to return 1 and I switch windows, like I should have from the beginning.

I need a way to tell if I'm a tmux session or not, which is not too hard since tmux sets the TMUX and TERM variables as described here, except skhd executes the commands in a subsession, so all those check are always going to say that I'm not in a tmux session.

I'm sorry to be asking more of your time, but I really couldn't come up with a solution myself.

noib3 avatar Mar 20 '20 00:03 noib3

Im interested to know whether this helps you, https://superuser.com/questions/410017/how-do-i-know-current-tmux-session-name-by-running-tmux-command

Some of these solutions rely on the env vars but it looks like some are a bit more robust I might wind up implementing bindings similar to yours, so I'm curious to know the outcome

strazto avatar Mar 20 '20 01:03 strazto

@mstr3336 I don't think those solutions apply here. The issue is not that using the TMUX or TERM variables to decide if I'm in a tmux session or not is not a robust way, it's that skhd (obviously) doesn't execute its commands from the terminal window I'm focusing, instead it executes them from a subsession. This means that every check is going to say that I am not in a tmux session.

I need a way to say: hey skhd, I have this command to execute, but you should first check if the terminal window I was focusing is within a tmux session.

Right now I'm saying: hey skhd, I have this command to execute, but you should first check if you are within a tmux session.

noib3 avatar Mar 20 '20 11:03 noib3

@synchronizing I'm not trying to share the same keybindings between tmux panes and vim splits, I'm trying to share them between tmux panes and all the other programs (browser, pdf viewer, etc...)

noib3 avatar Mar 30 '20 00:03 noib3

Could you set the title of the terminal window to be e.g. "TMUX" when you run tmux? Then, if you use yabai as a window manager you can query the title of the focused window as is done here https://github.com/koekeishiya/yabai/issues/75

slarwise avatar Apr 11 '20 11:04 slarwise

I'm trying to make it also work seamlessy with vim following https://www.codeography.com/2013/06/19/navigating-vim-and-tmux-splits this guide and placing in my skhdrc:

ctrl - h [
  *           : yabai -m window --focus west
  "Alacritty" : if [ $(tmux display-message -p '#{pane_at_left}') -ne 1 ]; then tmux run "(tmux display-message -p '#{pane_title}' | grep -iq vim && tmux send-keys C-h) || tmux select-pane -L"; else yabai -m window --focus west || true; fi
]

ctrl - j [
  *           : yabai -m window --focus south
  "Alacritty" : if [ $(tmux display-message -p '#{pane_at_bottom}') -ne 1 ]; then tmux run "(tmux display-message -p '#{pane_title}' | grep -iq vim && tmux send-keys C-j) || tmux select-pane -D" ; else yabai -m window --focus south || true; fi
]

ctrl - k [
  *           : yabai -m window --focus north
  "Alacritty" : if [ $(tmux display-message -p '#{pane_at_top}') -ne 1 ]; then tmux run "(tmux display-message -p '#{pane_title}' | grep -iq vim && tmux send-keys C-k) || tmux select-pane -U"; else yabai -m window --focus north || true; fi
]

ctrl - l [
  *           : yabai -m window --focus east
  "Alacritty" : if [ $(tmux display-message -p '#{pane_at_right}') -ne 1 ]; then tmux run "(tmux display-message -p '#{pane_title}' | grep -iq vim && tmux send-keys C-l) || tmux select-pane -R"; else yabai -m window --focus east || true; fi
]

This still works for tmux but not for vim :( Does anyone know how could we solve this ? ctrl - h sometimes seems to work

danielfalbo avatar Jun 30 '20 08:06 danielfalbo

Maybe it's because skhd keys have higher priority than vim keys, but if we're in vim should we let vim do the work ?

danielfalbo avatar Jun 30 '20 09:06 danielfalbo

https://github.com/christoomey/vim-tmux-navigator

Hm, seems like you're asking tmux directly to do the work. I'm not sure that that really gives vim a chance to pick up the keypress and then delegate to the function

strazto avatar Jul 02 '20 00:07 strazto

Tried it but couldn't get it to work :(

danielfalbo avatar Jul 02 '20 09:07 danielfalbo

Does vim tmux navigator work correctly without skhd being involved? I'll share my dotfiles soon

strazto avatar Jul 02 '20 10:07 strazto

Yes, works fine

danielfalbo avatar Jul 02 '20 10:07 danielfalbo

I'm currently navigating seamlessly between tmux panes and windows with yabai with the method shared above (https://github.com/koekeishiya/skhd/issues/116#issuecomment-599045162) but using ordinary keys for vim splits navigation

danielfalbo avatar Jul 02 '20 10:07 danielfalbo

I finally fixed everything, now I'm able to seamless navigate between vim splits, tmux panes and yabai windows with the same keys https://github.com/danielfalbo/dotfiles/commit/1f40ca3d44bdd1b4838ce24c2afae09ae7a9bbb2 😍

danielfalbo avatar Nov 06 '20 06:11 danielfalbo

For some reason seems like it does not work if I'm in a vim split at the edge of the terminal and I want to move to the adjacent yabai window

danielfalbo avatar Nov 06 '20 06:11 danielfalbo

I thing I got it now https://github.com/danielfalbo/dotfiles/commit/a485c9f080e340ec9af24b5145fc99ad3848f9f5 :)

danielfalbo avatar Nov 06 '20 07:11 danielfalbo

@danielfalbo link to commits are broken?

muhammad-saleh avatar Jun 05 '21 11:06 muhammad-saleh

@danielfalbo link to commits are broken?

Yeah, they're broken because I switched to a bare dotfiles repo and deleted the old one.

Anyway, I'm not using the vim-tmux-yabai thing anymore, I'm now using iTerm's built-in tmux integration, so I just create new native iTerm windows, which are managed by tmux, and simply use yabai to jump between the windows.

https://user-images.githubusercontent.com/39460524/120892593-6b2f4080-c60f-11eb-9a15-810efb39679e.mov

It's not quite the same and it may or may not be the best solution based on your workflow, so if you're still trying to achieve vim-tmux-yabai navigation, try the following.

~/.bin/tmux-yabai.sh

#! /usr/bin/env bash

YABAI_DIRECTION=$1

case $1 in
  "west")
    PANE_DIRECTION="left"
    DIRECTION_FLAG="-L"
    ;;
  "south")
    PANE_DIRECTION="bottom"
    DIRECTION_FLAG="-D"
    ;;
  "north")
    PANE_DIRECTION="top"
    DIRECTION_FLAG="-U"
    ;;
  "east")
    PANE_DIRECTION="right"
    DIRECTION_FLAG="-R"
    ;;
esac

if [[ $(tmux display-message -p "#{pane_at_${PANE_DIRECTION}}") == "0" ]]; then
  tmux select-pane ${DIRECTION_FLAG}
else
  yabai -m window --focus ${YABAI_DIRECTION} || true
fi

(assuming ~/.bin is in your PATH)

~/.skhdrc

ctrl - h [
  *           : yabai -m window --focus west
  "Terminal"  ~
]

ctrl - j [
  *           : yabai -m window --focus south
  "Terminal"  ~
]

ctrl - k [
  *           : yabai -m window --focus north
  "Terminal"  ~
]

ctrl - l [
  *           : yabai -m window --focus east
  "Terminal"  ~
]

(replace "Terminal" with your terminal emulator)

~/.tmux.conf

is_vim="ps -o state= -o comm= -t '#{pane_tty}' | grep -iqE '^[^TXZ ]+ +(\\S+\\/)?g?(view|n?vim?x?)(diff)?$'"

bind-key -n 'C-h' if-shell "$is_vim" 'send-keys C-h'  'run "tmux-yabai.sh west"'
bind-key -n 'C-j' if-shell "$is_vim" 'send-keys C-j'  'run "tmux-yabai.sh south"'
bind-key -n 'C-k' if-shell "$is_vim" 'send-keys C-k'  'run "tmux-yabai.sh north"'
bind-key -n 'C-l' if-shell "$is_vim" 'send-keys C-l'  'run "tmux-yabai.sh east"'

~/.vimrc

function! TmuxYabaiOrSplitSwitch(wincmd, direction)
  let previous_winnr = winnr()
  silent! execute "wincmd " . a:wincmd
  if previous_winnr == winnr()
    call system("tmux-yabai.sh " . a:direction)
  endif
endfunction

nnoremap <silent> <C-h> :call TmuxYabaiOrSplitSwitch('h', 'west')<cr>
nnoremap <silent> <C-j> :call TmuxYabaiOrSplitSwitch('j', 'south')<cr>
nnoremap <silent> <C-k> :call TmuxYabaiOrSplitSwitch('k', 'north')<cr>
nnoremap <silent> <C-l> :call TmuxYabaiOrSplitSwitch('l', 'east')<cr>

https://user-images.githubusercontent.com/39460524/120894655-e1d13b80-c619-11eb-86ab-d2383a727678.mov

I quickly rewrote it, if you find bugs just let me know :)

danielfalbo avatar Jun 05 '21 14:06 danielfalbo

@danielfalbo Thank you! I really appreciate your help. Sometimes it doesn't work and it says tmux-yabai.sh east returned 127

muhammad-saleh avatar Jun 05 '21 17:06 muhammad-saleh

Please provide an example scenario so I can reproduce the issue 👍

danielfalbo avatar Jun 05 '21 17:06 danielfalbo

@danielfalbo It happens when in one screen I have 2 browser windows open and I open an Alacritty window, I move around find till I activate the Alacritty window, then the shortcuts doesn't work and I can't leave the Alacritty window

Also the error I mentioned above happens when I have one Alacritty window running 2 tmux panes and vim is running in one (with multiple splits) and one is running a script (for example a dev server running and watching files) then the stdout of the server gets clear and I see the error message tmux-yabai.sh north returned 127 and the top panes are not activated, If I enter the shortcut again it works

muhammad-saleh avatar Jun 05 '21 17:06 muhammad-saleh

when in one screen I have 2 browser windows open and I open an Alacritty window, I move around find till I activate the Alacritty window, then the shortcuts doesn't work and I can't leave the Alacritty window

Oh, I see. It doesn't work if the terminal emulator is not running tmux, and I'm not sure it works if there are multiple terminal emulator windows running different tmux sessions. Right now I can't think of a solution to this, solving this would probably also solve the issue discussed above.

A temporary workaround may be mapping 'Ctrl+{h, j, k, l}' to 'yabai -m window --focus {west, south, north, east}' in your shell when the variable TMUX is not set, but of course it won't work with other subprocesses.

when I have one Alacritty window running 2 tmux panes and vim is running in one (with multiple splits) and one is running a script (for example a dev server running and watching files) then the stdout of the server gets clear and I see the error message tmux-yabai.sh north returned 127 and the top panes are not activated, If I enter the shortcut again it works

Try giving execution permissions to the script by running chmod +x ~/.bin/tmux-yabai.sh and make sure to add ~/.bin to your PATH in your ~/.bashrc or equivalent, for example I have PATH=$PATH:~/.bin in my ~/.zshrc.

danielfalbo avatar Jun 05 '21 18:06 danielfalbo