Skip to content

Navigate seamlessly between system windows, vim splits and tmux panes by only using awesomewm navigation keybindings.

Notifications You must be signed in to change notification settings

intrntbrn/awesomewm-vim-tmux-navigator

Folders and files

NameName
Last commit message
Last commit date

Latest commit

Β 

History

47 Commits
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 

Repository files navigation

AwesomeWM - Vim - Tmux Navigator

Usually vim and tmux have their own dedicated keybinds for navigation. Christoomey's plugin vim-tmux-navigator allows you to the use the same keybinds for both of them. This might be sufficient for floating wm users, but when using a tiling wm like awesomewm we can do better and add another layer.

awesomewm-vim-tmux-navigator lets you navigate seamlessly between system windows, (n)vim splits and tmux panes by only using your awesomewm navigation keybinds (e.g. Mod4+hjkl). Every split and pane is treated like a standalone system window, allowing you to forget your vim/tmux specific navigation hotkeys.

πŸ’‘ How does it work?

It essentially works by emulating the correct keypresses based on the context. Therefore the plugin has to detect whether the current focused application is vim, tmux, vim inside tmux (etc.) or any other system window.

The plugin offers two methods of determining the focused application:

  1. By using dynamic titles. The plugin tries to change the title of your terminal in order to differentiate between applications. However, not every shell-terminal-stack supports dynamic titles or is configured correctly out of the box.

  2. By using pstree. This should theoretically work on every setup, but it might perform slightly slower due to having an extra process to spawn.

By default the plugin uses awesomewm's builtin implementation (root.fake_input) to emulate keypresses.

πŸ“¦ Installation

The configuration of vim or tmux is optional if you only use one of them.

awesomewm:

Clone the repo:

git clone https://github.com/intrntbrn/awesomewm-vim-tmux-navigator ~/.config/awesome/awesomewm-vim-tmux-navigator

Import and configure the module:

require("awesomewm-vim-tmux-navigator")({
	mod = "Mod4",
	mod_keysym = "Super_L", -- comment out to autodetect
	up = { "Up", "k" },
	down = { "Down", "j" },
	left = { "Left", "h" },
	right = { "Right", "l" },

	tmux = {
		mods = { "Control_L" },
		up = "Up",
		down = "Down",
		left = "Left",
		right = "Right",
	},

	vim = {
		mods = { "Control_L" },
		up = "k",
		down = "j",
		left = "h",
		right = "l",
	},

	-- focus = require("awful").client.focus.global_bydirection,
	-- debug = print,

	-- dont_restore_mods = true, -- prevent sticky mods (see troubleshooting)
	-- use_pstree = true, -- detect app by using pstree instead of dynamic titles
	-- use_xdotool = true, -- emulate keypresses using xdotool instead of builtin
})

If the awesomewm navigation keybinds are already in use, you have to remove them manually in your rc.lua.

The corresponding keysym names can be retrieved by running the terminal command xev -event keyboard. If mod_keysym is nil the plugin tries to detect your modifier.

vim:

This plugin replaces christoomey/vim-tmux-navigator. Therefore you have to replace it in your plugin manager with intrntbrn/awesomewm-vim-tmux-navigator. However, both plugins share the same keybind commands, so it's not necessary to adjust already configured custom keybinds. Custom keybinds have to match your awesomewm configuration.

lazy.nvim (lua):

 {
	"intrntbrn/awesomewm-vim-tmux-navigator",
	event = "VeryLazy",
	build = "git -C ~/.config/awesome/awesomewm-vim-tmux-navigator/ pull",
	init = function()
		vim.g.tmux_navigator_no_mappings = 1
		-- vim.g.tmux_navigator_no_dynamic_title = 1
		-- vim.g.tmux_navigator_save_on_switch = 1
		-- vim.g.tmux_navigator_disable_when_zoomed = 1
		-- vim.g.tmux_navigator_preserve_zoom = 1

		vim.keymap.set("n", "<C-h>", ":TmuxNavigateLeft<CR>", { noremap = true, silent = true })
		vim.keymap.set("n", "<C-j>", ":TmuxNavigateDown<CR>", { noremap = true, silent = true })
		vim.keymap.set("n", "<C-k>", ":TmuxNavigateUp<CR>", { noremap = true, silent = true })
		vim.keymap.set("n", "<C-l>", ":TmuxNavigateRight<CR>", { noremap = true, silent = true })
	end,
}
vim-plug (VimL):
let g:tmux_navigator_no_mappings = 1
noremap <silent> <c-h> :<C-U>TmuxNavigateLeft<cr>
noremap <silent> <c-j> :<C-U>TmuxNavigateDown<cr>
noremap <silent> <c-k> :<C-U>TmuxNavigateUp<cr>
noremap <silent> <c-l> :<C-U>TmuxNavigateRight<cr>
" let g:tmux_navigator_no_dynamic_title = 1
" let g:tmux_navigator_save_on_switch = 1
" let g:tmux_navigator_disable_when_zoomed = 1
" let g:tmux_navigator_preserve_zoom = 1

Plug 'intrntbrn/awesomewm-vim-tmux-navigator', { do = 'git -C ~/.config/awesome/awesomewm-vim-tmux-navigator/ pull' }

tmux:

Add the following to your tmux.conf:

# smart pane switching with awareness of vim splits and awesomewm windows
is_vim="ps -o state= -o comm= -t '#{pane_tty}' | grep -iqE '^[^TXZ ]+ +(\\S+\\/)?g?(view|n?vim?x?)(diff)?$'"
bind-key -n 'C-Left' if-shell "$is_vim" { send-keys C-h } { if-shell -F '#{pane_at_left}'   {run-shell 'awesome-client "awesome.emit_signal(\"navigator::focus\", \"left\")" ' } { select-pane -L } }
bind-key -n 'C-Right' if-shell "$is_vim" { send-keys C-l } { if-shell -F '#{pane_at_right}'   {run-shell 'awesome-client "awesome.emit_signal(\"navigator::focus\", \"right\")" ' } { select-pane -R } }
bind-key -n 'C-Up' if-shell "$is_vim" { send-keys C-k } { if-shell -F '#{pane_at_top}'   {run-shell 'awesome-client "awesome.emit_signal(\"navigator::focus\", \"up\")" ' } { select-pane -U } }
bind-key -n 'C-Down' if-shell "$is_vim" { send-keys C-j } { if-shell -F '#{pane_at_bottom}'   {run-shell 'awesome-client "awesome.emit_signal(\"navigator::focus\", \"down\")" ' } { select-pane -D } }
bind-key -T copy-mode-vi 'C-Left' if-shell "$is_vim" { send-keys C-h } { if-shell -F '#{pane_at_left}'   {run-shell 'awesome-client "awesome.emit_signal(\"navigator::focus\", \"left\")" ' } { select-pane -L } }
bind-key -T copy-mode-vi 'C-Right' if-shell "$is_vim" { send-keys C-l } { if-shell -F '#{pane_at_right}'   {run-shell 'awesome-client "awesome.emit_signal(\"navigator::focus\", \"right\")" ' } { select-pane -R } }
bind-key -T copy-mode-vi 'C-Up' if-shell "$is_vim" { send-keys C-k } { if-shell -F '#{pane_at_top}'   {run-shell 'awesome-client "awesome.emit_signal(\"navigator::focus\", \"up\")" ' } { select-pane -U } }
bind-key -T copy-mode-vi 'C-Down' if-shell "$is_vim" { send-keys C-j } { if-shell -F '#{pane_at_bottom}'   {run-shell 'awesome-client "awesome.emit_signal(\"navigator::focus\", \"down\")" ' } { select-pane -D } }

# set title suffix to "- TMUX" (optional when using pstree method)
set-option -g set-titles on
set-option -g set-titles-string '#S: #W - TMUX'

Please note that if you're using custom vim keybinds, you have to edit the { send-keys <keybind> } section for each tmux bind to match your vim configuration.

❓ Troubleshooting

Setup:

  1. Enable debug mode:
debug = function(msg) require("naughty").notify({ text = msg }) end
  1. Make sure there are no conflicting keybinds. You can bypass conflicts in awesomewm by invoking the plugin from the terminal for verification:
echo "select a window" && sleep 2 && awesome-client 'awesome.emit_signal("navigator::navigate", "up")'
  1. Verify if dynamic titles are working. The title of (n)vim and tmux applications should end with "- (N)VIM" or "- TMUX" respectively. If you don't have a titlebar, you can use the terminal command xprop | grep WM_NAME. If dynamic titles are not working, set use_pstree to true or configure your shell-terminal-stack correctly. Minimal configurations are provided for zsh and bash:
echo "source ~/.config/awesome/awesomewm-vim-tmux-navigator/dynamictitles.zsh" >> ~/.zshrc
echo "source ~/.config/awesome/awesomewm-vim-tmux-navigator/dynamictitles.bash" >> ~/.bashrc
  1. Check https://github.com/christoomey/vim-tmux-navigator#troubleshooting.

  2. Create an issue.

Sticky Mods:

In order to emulate keypresses in vim/tmux, the plugin needs to release your wm modifier (e.g. mod4) temporarily and restore it afterwards. There is a race condition if the user was fast enough to release the modifier during this operation resulting in sticky mods. This issue is very unlikely to be ever solved.

Restoring mods can be disabled by setting the dont_restore_mods option. Please note that it's not possible to hold down the modifier for consecutive actions using this option. However, this side effect can be circumvented by assigning combo-keybinds using custom keyboards (qmk or similar software/firmware).

πŸ“‘ API

  • navigate: directional vim/tmux aware navigation
  • focus: regular directional system window navigation

awesomewm:

awesome.emit_signal("navigator::navigate", "left")
awesome.emit_signal("navigator::focus", "right")

shell:

awesome-client 'awesome.emit_signal("navigator::navigate", "up")'
awesome-client 'awesome.emit_signal("navigator::focus", "down")'

Releases

No releases published

Packages

No packages published