Files
dotfiles/.config/tmux/tmux.conf
Julian Prein 59863b28b3 tmux: Fix scrolling down in alternate mode
Scrolling down with the mouse wheel in alternate mode (e.g. less)
stopped working for me. I'm pretty sure that this used to work, but tmux
never had a default binding for WheelDownPane, so I'm unsure from where
this change should have come from ¯\_(ツ)_/¯
2025-08-06 11:38:24 +02:00

452 lines
14 KiB
Bash

set -g default-terminal "tmux-256color"
set -g mouse on
set -g mode-keys vi
set -g extended-keys always
if -F "#{>=:#{version},3.3}" {
set -g prompt-history-limit 999999
}
set -g history-file "$XDG_DATA_HOME/tmux/tmux_history"
run-shell 'mkdir -p "${XDG_DATA_HOME:-$HOME/.local/share}/tmux"'
# Better clipboard on mousedrag (https://unix.stackexchange.com/a/349020)
# Disable xterm escape sequences for setting clipboard
set -s set-clipboard off
set -g copy-command 'xclip -selection clipboard'
# https://github.com/neovim/neovim/wiki/FAQ#cursor-shape-doesnt-change-in-tmux
set -ga terminal-overrides '*:Ss=\E[%p1%d q:Se=\E[ q'
# Set C-a as new prefix
unbind C-b
set -g prefix C-a
bind C-a send-prefix
# Copy selection into primary selection (without aborting)
bind -T copy-mode-vi MouseDragEnd1Pane {
send -X copy-pipe-no-clear "xclip -selection primary"
}
# Select word or line on double or triple click respectively (w/o aborting).
# (These are the defaults w/o the `run -d 0.3; send -X copy-pipe-and-cancel`)
# use 'SecondClick' event instead of 'DoubleClick' for a quicker response. The
# third click would just extend the selection further so the eager execution
# does not hurt.
unbind -T copy-mode-vi DoubleClick1Pane
bind -T copy-mode-vi SecondClick1Pane {
select-pane -t=
send -X select-word
send -X pipe-no-clear "xclip -selection primary"
}
bind -T copy-mode-vi TripleClick1Pane {
select-pane -t=
send -X select-line
send -X pipe-no-clear "xclip -selection primary"
}
# use 'SecondClick' event instead of 'DoubleClick' (see above).
unbind -n DoubleClick1Pane
bind -n SecondClick1Pane {
select-pane -t=
if -F "#{||:#{pane_in_mode},#{mouse_any_flag}}" {
send -M
} {
copy-mode
send -X select-word
send -X pipe-no-clear "xclip -selection primary"
}
}
bind -n TripleClick1Pane {
select-pane -t=
if -F "#{||:#{pane_in_mode},#{mouse_any_flag}}" {
send -M
} {
copy-mode
send -X select-line
send -X pipe-no-clear "xclip -selection primary"
}
}
# Paste primary selection with middle mouse button
bind -n MouseDown2Pane {
select-pane -t=
if -F "#{||:#{pane_in_mode},#{mouse_any_flag}}" {
send -M
} {
run 'tmux set-buffer -b XA_PRIMARY "$(xclip -selection p -o)"'
paste-buffer -p -b XA_PRIMARY
}
}
# When in copy-mode do the same but leave copy-mode first
bind -T copy-mode-vi MouseDown2Pane {
select-pane -t=
send -X cancel
# TODO: Forward click to 'root' key-table to prevent code duplication
run 'tmux set-buffer -b XA_PRIMARY "$(xclip -selection p -o)"'
paste-buffer -p -b XA_PRIMARY
}
# Scroll up 5 lines when entering copy-mode through the mouse wheel, send 5x`up`
# when the current pane is in alternate mode and discard the `-e` flag passed to
# copy-mode from the default binding (See below)
bind -n WheelUpPane {
if -F "#{||:#{pane_in_mode},#{mouse_any_flag}}" {
send -M
} {
if -F "#{alternate_on}" {
send -N5 up
} {
copy-mode
send -N5 C-y
}
}
}
# Scroll without selecting the pane
bind -T copy-mode-vi WheelUpPane send -N5 C-y
# Exit copy-mode when at the bottom. This practically mimics `copy-mode -e` but
# only scrolling with the wheel (`-e` annoys me when scrolling down with `C-d`).
bind -T copy-mode-vi WheelDownPane {
if -F "#{==:0,#{scroll_position}}" {
send -X cancel
} {
send -N5 C-e
}
}
# Make mouse wheel scroll down properly in alternate mode
bind -n WheelDownPane {
send -M
if -F "#{||:#{pane_in_mode},#{mouse_any_flag}}" {
send -M
} {
if -F "#{alternate_on}" {
send -N5 down
} {
# we're already at the bottom of the history, so nothing
# needs to be done here
}
}
}
# Split panes with > and <
unbind %
bind < splitw -v -c "#{pane_current_path}"
unbind '"'
bind > splitw -h -c "#{pane_current_path}"
# tmux stopped supporting format string for -t, thus run-shell is
bind -N "Create a new window behind the current one with the same CWD" \
c run-shell \
"tmux neww -at #{active_window_index} -c \"#{pane_current_path}\""
bind -N "Break off the current pane to a new window behind the current one" \
! run-shell "tmux breakp -at #{active_window_index}"
bind -N "Source the config file" \
R \
source-file "$XDG_CONFIG_HOME/tmux/tmux.conf" \; \
display "Sourced $XDG_CONFIG_HOME/tmux/tmux.conf"
# Fullscreen pane (toggle)
bind F resizep -Z
# Automatically renumber windows when closing one
set -g renumber-windows on
# Number windows and panes with 1-based indices
set -g base-index 1
set -g pane-base-index 1
# Enter copy-mode and scroll one page up directly with PageUp without the need
# of the prefix. (Exists already per default with prefix)
bind -n PgUp copy-mode -u
# Repeatable window-navigation bindings
bind -r n next
bind -r C-n next
bind -r p prev
bind -r C-p prev
# Change windows with Alt-Page{Up,Down} analog to Ctrl-Page{Up,Down} in vim
bind -n M-PgUp prev
bind -n M-PgDn next
# Jump back to last focused window
bind C-o last-window
# Swap window with previous/next one
bind -r N swap-window -d -t :+1
bind -r P swap-window -d -t :-1
# Reorder windows by dragging them in the status line.
# NOTE: Omitting the source from 3.4 on (after 8636848e6348) will do nothing, as
# the source is then implied to be the window that the mouse is currently
# on (i.e. also the target)
# TODO: comment on tmux issue #656
if -F "#{>=:#{version},3.4}" {
# TODO: why does this not work <3.4?
bind -n MouseDrag1Status swap-window -d -s"" -t=
} {
bind -n MouseDrag1Status swap-window -d -t=
# TODO: why does this not work >=3.4?
# bind -n MouseDrag1Status swap-window -d -s":" -t=
}
# Move a window to the first or very last position
# TODO: do nothing on drags that don't start on a window name
# NOTE: mouse_{x,y} were not set in status line before 3.3
if -F "#{>=:#{version},3.3}" {
bind -n MouseDragEnd1StatusDefault {
if -F "#{e|<|f|0:#{mouse_x},#{e|/|f:#{window_width},2}}" {
move-window -bt :^
} {
move-window -at :$
}
}
}
# Repeatable pane swapping bindings
bind -r \{ swap-pane -U
bind -r \} swap-pane -D
# Select last window if target is current
bind 1 select-window -T -t 1
bind 2 select-window -T -t 2
bind 3 select-window -T -t 3
bind 4 select-window -T -t 4
bind 5 select-window -T -t 5
bind 6 select-window -T -t 6
bind 7 select-window -T -t 7
bind 8 select-window -T -t 8
bind 9 select-window -T -t 9
# Since base-index == 1, 0 should go to window 10
bind 0 select-window -T -t 10
# Select windows with Alt-[0-9], select last window if target is current
bind -n M-1 select-window -T -t 1
bind -n M-2 select-window -T -t 2
bind -n M-3 select-window -T -t 3
bind -n M-4 select-window -T -t 4
bind -n M-5 select-window -T -t 5
bind -n M-6 select-window -T -t 6
bind -n M-7 select-window -T -t 7
bind -n M-8 select-window -T -t 8
bind -n M-9 select-window -T -t 9
bind -n M-0 select-window -T -t 10
# Vim-bindings
# Enter copy-mode with Escape
# bind Escape copy-mode
# Start selection (i.e. visual mode) with `v`, and rectangle selection (i.e.
# visual block mode) with `C-v`
# TODO: mimic vims behaviour for visual mode cycling and toggling
unbind -T copy-mode-vi v
bind -T copy-mode-vi v send -X begin-selection
unbind -T copy-mode-vi C-v
bind -T copy-mode-vi C-v {
if -F "#{selection_active}" {} { send -X begin-selection }
send -X rectangle-toggle
}
# Yank into system clipboard with vim-like bindings
bind -T copy-mode-vi y run-shell '${XDG_CONFIG_HOME:-$HOME/.config}/tmux/yank.sh'
bind -T copy-mode-vi Y {
if -F "#{selection_present}" {
send -X copy-pipe-line
} {
send -X copy-pipe-end-of-line
}
}
# Clear selection or cancel copy-mode when nothing is selected
bind -T copy-mode-vi Escape {
if -F "#{selection_present}" {
send -X clear-selection
} {
send -X cancel
}
}
# Use insert-mode-like bindings to cancel copy-mode
bind -T copy-mode-vi A send -X cancel
bind -T copy-mode-vi I send -X cancel
bind -T copy-mode-vi a send -X cancel
# Rudimentary text object simulation
bind -T copy-mode-vi i run-shell '${XDG_CONFIG_HOME:-$HOME/.config}/tmux/textobjs.sh'
# Simulate vim's C-e & C-y (Keep cursor position while scrolling). As tmux
# cannot scroll past the first and last history line, both bindings stop
# scrolling when the respective line is visible. This mimics the behaviour of
# vim's C-y at the top of the file.
bind -T copy-mode-vi C-e {
# check that there is still history beneath to scroll to.
if -F "#{e|>:#{scroll_position},0}" {
send-keys -X scroll-down
# check that the cursor is not at the top of the pane
if -F "#{e|>:#{copy_cursor_y},#{scroll_region_upper}}" {
send-keys -X cursor-up
}
}
}
bind -T copy-mode-vi C-y {
# check that there is still history above to scroll to.
if -F "#{e|<:#{scroll_position},#{history_size}}" {
send-keys -X scroll-up
# check that the cursor is not at the bottom of the pane
if -F "#{e|<:#{copy_cursor_y},#{scroll_region_lower}}" {
send-keys -X cursor-down
}
}
}
# Scroll so that the current line becomes the middle one (vim's zz)
if -F "#{<:#{version},3.4}" {
# Simulate when scroll-middle does not exist yet
# TODO: do the scroll_position and cursor_y calculations only once here
bind -T copy-mode-vi z {
set -F @middle_line "#{e|/:#{e|-:#{pane_height},1},2}"
set -F @scroll_to_middle "#{e|-:#{copy_cursor_y},#{@middle_line}}"
if -F "#{e|<:#{@scroll_to_middle},0}" {
run "tmux send -N #{e|*:-1,#{@scroll_to_middle}} C-y"
} { if -F "#{e|>:#{@scroll_to_middle},0}" {
run "tmux send -N #{@scroll_to_middle} C-e"
} }
}
} {
# should be the default, but here for verboseness
bind -T copy-mode-vi z send -X scroll-middle
}
# Navigate panes with <prefix>-[hjkl]
# NOTE: C-[hjkl] (w/o prefix) moves through vim splits and tmux panes
# See vim-tmux-navigator
unbind l
bind h selectp -L
bind j selectp -D
bind k selectp -U
bind l selectp -R
unbind -T prefix Left
unbind -T prefix Up
unbind -T prefix Down
unbind -T prefix Right
# Resize mode similar to my i3 config
bind -T resize h resizep -L \; switchc -T resize
bind -T resize j resizep -D \; switchc -T resize
bind -T resize k resizep -U \; switchc -T resize
bind -T resize l resizep -R \; switchc -T resize
bind -T resize 1 selectl even-horizontal \; switchc -T resize
bind -T resize 2 selectl even-vertical \; switchc -T resize
bind -T resize 3 selectl main-horizontal \; switchc -T resize
bind -T resize 4 selectl main-vertical \; switchc -T resize
bind -T resize 5 selectl tiled \; switchc -T resize
bind r switchc -T resize
# Make it possible to send keys that are bound in the root key table. (C-h for
# example is sometimes needed as backspace sequence)
# TODO: Make repeatable without C-v
bind -r C-v command-prompt -k -p (selfinsert) { send "%%" }
# Synchronize the panes in the current window with `<prefix>S`
bind S set -w synchronize-panes
# Require confirmation before killing a pane
set -g remain-on-exit on
if -F "#{>=:#{version},3.3}" {
bind -n C-d if -F "#{pane_dead}" { kill-pane } { send }
bind -n Enter if -F "#{pane_dead}" { respawn-pane } { send }
} {
# omitting the key argument was introduced in 3.3
bind -n C-d if -F "#{pane_dead}" { kill-pane } { send C-d }
bind -n Enter if -F "#{pane_dead}" { respawn-pane } { send Enter }
}
# Change installation location of plugins
setenv -g TMUX_PLUGIN_MANAGER_PATH "$XDG_CONFIG_HOME/tmux/plugins/"
## Plugins
set -g @plugin 'tmux-plugins/tmux-continuum'
set -g @plugin 'tmux-plugins/tmux-prefix-highlight'
set -g @plugin 'tmux-plugins/tmux-resurrect'
set -g @plugin 'tmux-plugins/tmux-sensible'
set -g @plugin 'tmux-plugins/tpm'
set -g @plugin 'vim-tmux-navigator'
# Capture pane content
set -g @resurrect-capture-pane-contents 'on'
# Change keybindings
set -g @resurrect-save 'C-s'
set -g @resurrect-restore 'C-r'
# Save session every 5 min
set -g @continuum-save-interval '5'
# Last saved environment is automatically restored when tmux is started.
set -g @continuum-restore 'on'
# Restore {,neo}vim sessions with the help of vim-obsession
# https://github.com/tpope/vim-obsession
set -g @resurrect-strategy-vim 'session'
set -g @resurrect-strategy-nvim 'session'
# Save sessions before detaching
unbind d
bind d {
run -b "$XDG_CONFIG_HOME/tmux/plugins/tmux-resurrect/scripts/save.sh"
detach
}
## Theming
set -g @accent_color '#fce94f' # brightyellow
set -g @ui_color '#757773' # brightblack
set -g @sync_color '#ef2929' # brightred
set -g @text_fg '#5e6466' # black
set -g status-style bg=default,fg='#d3d7cf' # white
set -g message-command-style 'bg=default,fg=#{@accent_color}'
set -g message-style 'bg=#{@accent_color},fg=#{@text_fg}'
set -g mode-style 'bg=#{@accent_color},fg=#{@text_fg}'
# Visually display when a pane is in copy-mode or 'synchronize-panes' is on.
# Use the pane-borders for tmux >= 3.2 and the "prefix_highlight" plugin
# otherwise.
#
# NOTE: `<` does string comparison with `strcmp()`. This means that the
# condition will break from a double digit major version (e.g. `10.0`) on.
# TODO: Use #{e|<|f:, but cut off trailing non-numbers and convert the result
# (i.e. 1.00 or 0.00) back into an int.
if -F "#{<:#{version},3.2}" {
set -g pane-border-style 'bg=default,fg=#{@ui_color}'
set -g pane-active-border-style 'bg=default,fg=#{@accent_color}'
set -g @prefix_highlight_show_copy_mode 'on'
set -g @prefix_highlight_show_sync_mode 'on'
set -g @prefix_highlight_copy_mode_attr 'fg=#{@text_fg},bg=#{@accent_color}'
set -g @prefix_highlight_sync_mode_attr 'fg=#{@text_fg},bg=#{@sync_color}'
} {
# From 3.2 on, we can use formats in styles, so that all indicators can be
# done by the borders.
set -g pane-border-style "#{?synchronize-panes,fg=#{@sync_color},fg=#{@ui_color}}"
set -g pane-active-border-style "#{?pane_in_mode,bg=#{@accent_color}#,fg=#{@ui_color},#{?synchronize-panes,fg=#{@sync_color},fg=#{@accent_color}}}"
}
set -g @prefix_highlight_fg '#{@accent_color}'
set -g @prefix_highlight_bg 'default'
set -g status-right "#{prefix_highlight}"
set -g status-left "#{prefix_highlight}"
set -g status-justify centre
# Display window_index and window_name as status and highlight it when the
# active pane is zoomed.
set -g window-status-current-format \
"#{?window_zoomed_flag,#[fg=#{@text_fg} bg=#{@accent_color} underscore],#[fg=#{@accent_color} bold]}#I #W"
set -g window-status-format \
"#[fg=#{@ui_color}]#{?window_zoomed_flag,#[underscore],}#I #W"
# Initialize TMUX plugin manager. Keep this line at the very bottom.
run -b "$XDG_CONFIG_HOME/tmux/plugins/tpm/tpm"