skhd
skhd copied to clipboard
Using hotkeys to move between tmux panes
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?
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?
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
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.
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.
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.
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)
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, sendskhd -k "alt - arrow"
and if it's not, do theyabai -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.
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.
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!
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:
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.
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
@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.
@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...)
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
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
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 ?
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
Tried it but couldn't get it to work :(
Does vim tmux navigator work correctly without skhd being involved? I'll share my dotfiles soon
Yes, works fine
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
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 😍
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
I thing I got it now https://github.com/danielfalbo/dotfiles/commit/a485c9f080e340ec9af24b5145fc99ad3848f9f5 :)
@danielfalbo link to commits are broken?
@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 Thank you! I really appreciate your help.
Sometimes it doesn't work and it says tmux-yabai.sh east returned 127
Please provide an example scenario so I can reproduce the issue 👍
@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
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
.