2026-10-14 13:32:57 -04:00
|
|
|
# Prefix: ctrl+b (Ghostty's ctrl+shift chords send it as \x02). ctrl+space
|
|
|
|
|
# is not a prefix: it opens tmux-palette (root table, see Plugins).
|
|
|
|
|
set -g prefix C-b
|
|
|
|
|
set -gu prefix2
|
|
|
|
|
unbind -q C-Space
|
|
|
|
|
bind -N "Send prefix" C-b send-prefix
|
|
|
|
|
|
|
|
|
|
set-option -g renumber-windows on
|
|
|
|
|
set -g base-index 1
|
|
|
|
|
setw -g pane-base-index 1
|
|
|
|
|
|
|
|
|
|
# Config and help
|
|
|
|
|
bind -N "Reload configuration" q source-file ~/.config/tmux/tmux.conf \; display "Configuration reloaded"
|
|
|
|
|
bind -N "Show Tmux keybindings" ? display-popup -E -w 80% -h 70% -T "Tmux keybindings" "if command -v omarchy-menu-tmux-keybindings >/dev/null; then omarchy-menu-tmux-keybindings --print; else tmux list-keys -N; fi | less -R"
|
|
|
|
|
|
|
|
|
|
# Vi mode for copy
|
|
|
|
|
setw -g mode-keys vi
|
|
|
|
|
bind -N "Begin selection" -T copy-mode-vi v send -X begin-selection
|
|
|
|
|
bind -N "Copy selection" -T copy-mode-vi y send -X copy-selection-and-cancel
|
|
|
|
|
bind -N "Search scrollback" / copy-mode \; command-prompt -p "search up:" "send -X search-backward '%%'"
|
|
|
|
|
|
|
|
|
|
# Pane Controls
|
|
|
|
|
bind -N "Split pane vertically" -n M-Enter split-window -v -c "#{pane_current_path}"
|
|
|
|
|
bind -N "Split pane horizontally" -n M-S-Enter split-window -h -c "#{pane_current_path}"
|
|
|
|
|
bind -N "Kill pane" -n M-Escape kill-pane
|
|
|
|
|
|
|
|
|
|
bind -N "Split pane vertically" h split-window -v -c "#{pane_current_path}"
|
|
|
|
|
bind -N "Split pane horizontally" v split-window -h -c "#{pane_current_path}"
|
|
|
|
|
bind -N "Kill pane" x kill-pane
|
|
|
|
|
|
|
|
|
|
bind -N "Focus pane left" -n C-M-Left select-pane -L
|
|
|
|
|
bind -N "Focus pane right" -n C-M-Right select-pane -R
|
|
|
|
|
bind -N "Focus pane up" -n C-M-Up select-pane -U
|
|
|
|
|
bind -N "Focus pane down" -n C-M-Down select-pane -D
|
|
|
|
|
|
|
|
|
|
# ctrl+h/j/k/l: vim-aware pane navigation, the tmux half of nvim's
|
|
|
|
|
# config.pane_nav. In an nvim/vim/fzf pane the key is forwarded so nvim moves
|
|
|
|
|
# between its own splits and, at its outer edge, hands off with
|
|
|
|
|
# `tmux select-pane -t $TMUX_PANE`; elsewhere tmux moves directly. "Is vim" is
|
|
|
|
|
# smart-splits' @pane-is-vim pane option or the pane's foreground command, read
|
|
|
|
|
# as a format (no ps fork per keypress). Ghostty passes these keys through
|
|
|
|
|
# (keybind = clear); prefix + ctrl+l is the clear-screen escape hatch.
|
|
|
|
|
bind -N "Focus pane left (vim-aware)" -n C-h if -F '#{||:#{@pane-is-vim},#{m/r:^(n?vim|vim|fzf)$,#{pane_current_command}}}' 'send-keys C-h' 'select-pane -L'
|
|
|
|
|
bind -N "Focus pane down (vim-aware)" -n C-j if -F '#{||:#{@pane-is-vim},#{m/r:^(n?vim|vim|fzf)$,#{pane_current_command}}}' 'send-keys C-j' 'select-pane -D'
|
|
|
|
|
bind -N "Focus pane up (vim-aware)" -n C-k if -F '#{||:#{@pane-is-vim},#{m/r:^(n?vim|vim|fzf)$,#{pane_current_command}}}' 'send-keys C-k' 'select-pane -U'
|
|
|
|
|
bind -N "Focus pane right (vim-aware)" -n C-l if -F '#{||:#{@pane-is-vim},#{m/r:^(n?vim|vim|fzf)$,#{pane_current_command}}}' 'send-keys C-l' 'select-pane -R'
|
|
|
|
|
bind -N "Focus pane left" -T copy-mode-vi C-h select-pane -L
|
|
|
|
|
bind -N "Focus pane down" -T copy-mode-vi C-j select-pane -D
|
|
|
|
|
bind -N "Focus pane up" -T copy-mode-vi C-k select-pane -U
|
|
|
|
|
bind -N "Focus pane right" -T copy-mode-vi C-l select-pane -R
|
|
|
|
|
bind -N "Clear screen" C-l send-keys C-l
|
|
|
|
|
|
|
|
|
|
bind -N "Resize pane left" -n C-M-S-Left resize-pane -L 5
|
|
|
|
|
bind -N "Resize pane down" -n C-M-S-Down resize-pane -D 5
|
|
|
|
|
bind -N "Resize pane up" -n C-M-S-Up resize-pane -U 5
|
|
|
|
|
bind -N "Resize pane right" -n C-M-S-Right resize-pane -R 5
|
|
|
|
|
|
|
|
|
|
# Window navigation
|
|
|
|
|
bind -N "Rename window" r command-prompt -I "#W" "rename-window -- '%%'"
|
|
|
|
|
bind -N "Create window" c new-window -c "#{pane_current_path}"
|
|
|
|
|
bind -N "Kill window" k kill-window
|
|
|
|
|
|
|
|
|
|
bind -N "Switch to window 1" -n M-1 select-window -t 1
|
|
|
|
|
bind -N "Switch to window 2" -n M-2 select-window -t 2
|
|
|
|
|
bind -N "Switch to window 3" -n M-3 select-window -t 3
|
|
|
|
|
bind -N "Switch to window 4" -n M-4 select-window -t 4
|
|
|
|
|
bind -N "Switch to window 5" -n M-5 select-window -t 5
|
|
|
|
|
bind -N "Switch to window 6" -n M-6 select-window -t 6
|
|
|
|
|
bind -N "Switch to window 7" -n M-7 select-window -t 7
|
|
|
|
|
bind -N "Switch to window 8" -n M-8 select-window -t 8
|
|
|
|
|
bind -N "Switch to window 9" -n M-9 select-window -t 9
|
|
|
|
|
|
|
|
|
|
bind -N "Previous window" -n M-Left select-window -t -1
|
|
|
|
|
bind -N "Next window" -n M-Right select-window -t +1
|
|
|
|
|
bind -N "Move window left" -n M-S-Left swap-window -t -1 \; select-window -t -1
|
|
|
|
|
bind -N "Move window right" -n M-S-Right swap-window -t +1 \; select-window -t +1
|
|
|
|
|
|
|
|
|
|
# Session controls
|
|
|
|
|
bind -N "Rename session" R command-prompt -I "#S" "rename-session -- '%%'"
|
|
|
|
|
bind -N "Create session" C new-session -c "#{pane_current_path}"
|
|
|
|
|
bind -N "Kill session" K kill-session
|
|
|
|
|
bind -N "Previous session" P switch-client -p
|
|
|
|
|
bind -N "Next session" N switch-client -n
|
|
|
|
|
|
|
|
|
|
bind -N "Previous session" -n M-Up switch-client -p
|
|
|
|
|
bind -N "Next session" -n M-Down switch-client -n
|
|
|
|
|
|
|
|
|
|
# General
|
|
|
|
|
set -g default-terminal "tmux-256color"
|
|
|
|
|
set -ag terminal-overrides ",*:RGB"
|
|
|
|
|
set -g mouse on
|
|
|
|
|
set -g base-index 1
|
|
|
|
|
setw -g pane-base-index 1
|
|
|
|
|
set -g renumber-windows on
|
|
|
|
|
set -g history-limit 50000
|
|
|
|
|
set -g escape-time 0
|
|
|
|
|
set -g focus-events on
|
|
|
|
|
# Yazi inside tmux: OSC 52 clipboard, image passthrough, and the outer
|
|
|
|
|
# TERM/TERM_PROGRAM so yazi detects Ghostty/kitty graphics. Never bind M-[:
|
|
|
|
|
# tmux would eat the ESC [ of yazi's terminal-query replies.
|
|
|
|
|
set -g set-clipboard on
|
|
|
|
|
set -g allow-passthrough on
|
|
|
|
|
set -ga update-environment TERM
|
|
|
|
|
set -ga update-environment TERM_PROGRAM
|
|
|
|
|
setw -g aggressive-resize on
|
|
|
|
|
# Killing a session switches only to a *detached* one, else detaches: with
|
|
|
|
|
# one session per Ghostty window, `off` would land on (and mirror) a session
|
|
|
|
|
# another window is showing.
|
|
|
|
|
set -g detach-on-destroy no-detached
|
|
|
|
|
set -g extended-keys on
|
|
|
|
|
set -g extended-keys-format csi-u
|
|
|
|
|
set -ag terminal-features "xterm-kitty:extkeys"
|
|
|
|
|
# shift+enter (Ghostty sends ESC[13;2u): pass it through as ESC[13;2u for
|
|
|
|
|
# coding agents (omp, claude) to insert a newline. With extended-keys `on`,
|
|
|
|
|
# tmux would flatten it to a plain Enter for any app that never asked for
|
|
|
|
|
# extended keys.
|
|
|
|
|
bind -N "Shift+Enter as CSI-u" -n S-Enter send-keys -H 1b 5b 31 33 3b 32 75
|
|
|
|
|
set -as terminal-features ",*:clipboard"
|
|
|
|
|
set -sg escape-time 10
|
|
|
|
|
|
|
|
|
|
# Status bar
|
|
|
|
|
set -g status-position top
|
|
|
|
|
set -g status-interval 5
|
|
|
|
|
set -g status-left-length 30
|
|
|
|
|
set -g status-right-length 50
|
|
|
|
|
set -g window-status-separator ""
|
|
|
|
|
set -gw automatic-rename on
|
|
|
|
|
# #{window_icon} is filled in by tmux-nerd-font-window-name (icons from
|
|
|
|
|
# tmux-nerd-font-window-name.yml); the label follows kitty's tab_bar.py. An
|
|
|
|
|
# editor shows the file from the title nvim sets ("<file> | <org/repo>", see
|
|
|
|
|
# nvim's astrocore custom_title), read straight from #{pane_title} so a buffer
|
|
|
|
|
# switch renames the tab at once; otherwise tab-label.sh prints the repo of the
|
|
|
|
|
# pane's cwd (an async #() job; it re-arms automatic-rename when the tab is
|
|
|
|
|
# stale, so a `cd` relabels without waiting for more pane output).
|
|
|
|
|
set -gw automatic-rename-format '#{window_icon} #{=/27/…:#{?#{&&:#{m/r:^(n?vim|vim|nvim-qt|hx|helix)$,#{pane_current_command}},#{m/r:^[^/|]+ \| ,#{pane_title}}},#{s/ \| .*$//:pane_title},#(~/.config/tmux/tab-label.sh #{q:pane_current_path} #{window_id})}}'
|
|
|
|
|
set -g set-titles on
|
|
|
|
|
# The terminal (Ghostty) window title is the session name: one session per
|
|
|
|
|
# Ghostty window, so compositor groups/tabs list sessions, not host:window.
|
|
|
|
|
set -g set-titles-string '#S'
|
|
|
|
|
|
|
|
|
|
# Theme — kitty's powerline/angled tab bar: the current window is an
|
|
|
|
|
# accent-tinted plate (accent mixed 20% into the background) with the accent
|
|
|
|
|
# as its text, the rest sit flush with the background in muted text. Fixed
|
|
|
|
|
# cyberdream palette, shared with Ghostty, nvim, yazi and television so every
|
|
|
|
|
# machine (Linux, mac, goliath) looks the same.
|
|
|
|
|
set -g @tab-bg "#16181a"
|
|
|
|
|
set -g @tab-fg "#7b8496"
|
|
|
|
|
set -g @tab-active-bg "#243348"
|
|
|
|
|
set -g @tab-active-fg "#5ea1ff"
|
|
|
|
|
|
|
|
|
|
set -g status-style "bg=default,fg=#{@tab-fg}"
|
|
|
|
|
# bg=default inside a tab format means "the window-status-*-style bg", not the
|
|
|
|
|
# terminal's, so these must stay default for the tabs to be transparent. Set
|
|
|
|
|
# explicitly: a config reload never clears a value a removed plugin left
|
|
|
|
|
# behind (tmux-dotbar painted window-status-style bg=#0B0E14).
|
|
|
|
|
set -g window-status-style default
|
|
|
|
|
set -g window-status-current-style default
|
|
|
|
|
set -g window-status-last-style default
|
|
|
|
|
set -g window-status-activity-style default
|
|
|
|
|
set -g window-status-bell-style default
|
|
|
|
|
# Tabs start right after the session pill (status-justify left; a reload
|
|
|
|
|
# would otherwise keep a stale value such as dotbar's absolute-centre), and
|
|
|
|
|
# the pill's arrow points into the first tab's plate.
|
|
|
|
|
set -g status-justify left
|
|
|
|
|
set -g status-left "#[fg=#{@tab-bg},bg=#{@tab-active-fg},bold] #S #[fg=#{@tab-active-fg},nobold]#{?#{==:#{active_window_index},#{base-index}},#[bg=#{@tab-active-bg}],#[bg=default]}"
|
|
|
|
|
set -g status-right "#[fg=#{@tab-active-fg}]#{?pane_in_mode,COPY ,}#{?client_prefix,PREFIX ,}#{?window_zoomed_flag,ZOOM ,}#[fg=#{@tab-fg}]#h "
|
|
|
|
|
# Inactive tabs are bg=default so the terminal's transparency shows through,
|
|
|
|
|
# as in kitty (tab_bar_background none). kitty's separator rule: a solid
|
|
|
|
|
# into a differently colored neighbour, thin between two inactive
|
|
|
|
|
# tabs, none after the last. The arrow into the current tab is painted in
|
|
|
|
|
# @tab-bg, the window background (Ghostty's cyberdream background, the same
|
|
|
|
|
# hex), so it blends into the window. (Plain is
|
|
|
|
|
# one of Ghostty's built-in sprites and fills the whole cell even with
|
|
|
|
|
# adjust-cell-height; inverse glyphs such as come from the font and
|
|
|
|
|
# leave gaps.)
|
|
|
|
|
set -g window-status-format "#[fg=#{@tab-fg},bg=default] #I #W #{?next_window_active,#[fg=#{@tab-bg}#,bg=#{@tab-active-bg}],#{?window_end_flag,,}}"
|
|
|
|
|
set -g window-status-current-format "#[fg=#{@tab-active-fg},bg=#{@tab-active-bg},bold] #I #W #[fg=#{@tab-active-bg},bg=default,nobold]"
|
|
|
|
|
set -g pane-border-style "fg=brightblack"
|
|
|
|
|
set -g pane-active-border-style "fg=#{@tab-active-fg}"
|
|
|
|
|
set -g message-style "bg=default,fg=#{@tab-active-fg}"
|
|
|
|
|
set -g message-command-style "bg=default,fg=#{@tab-active-fg}"
|
|
|
|
|
set -g mode-style "bg=#{@tab-active-bg},fg=#{@tab-active-fg}"
|
|
|
|
|
setw -g clock-mode-colour blue
|
|
|
|
|
|
|
|
|
|
# Plugins (tpm, cloned by .chezmoiexternal.toml). prefix + I installs,
|
|
|
|
|
# prefix + U updates, prefix + alt + u removes unlisted ones.
|
|
|
|
|
set -g @plugin 'tmux-plugins/tpm'
|
|
|
|
|
set -g @plugin 'tmux-plugins/tmux-sensible'
|
|
|
|
|
set -g @plugin 'joshmedeski/tmux-nerd-font-window-name'
|
|
|
|
|
set -g @plugin 'tmux-plugins/tmux-resurrect'
|
|
|
|
|
set -g @plugin 'tmux-plugins/tmux-continuum'
|
2026-10-16 11:10:43 -04:00
|
|
|
# prdlk fork of eduwass/tmux-palette: adds custom palettes' `query` item.
|
|
|
|
|
set -g @plugin 'prdlk/tmux-palette-plugin'
|
2026-10-14 13:32:57 -04:00
|
|
|
|
|
|
|
|
# tmux-palette: Raycast-style command palette on ctrl+space (no prefix; needs
|
|
|
|
|
# bun). User config (commands ported from kitty, cyberdream theme, Ghostty
|
|
|
|
|
# shortcut labels) lives in ~/.config/tmux-palette/.
|
|
|
|
|
set -g @palette-key 'C-Space'
|
|
|
|
|
# pj's task runner as a palette (pj-palette): quick actions + project tasks,
|
|
|
|
|
# one-offs in a bottom split, dev servers / compose up in a background
|
|
|
|
|
# <session>:<task> session popup. prefix + t (Ghostty ctrl+shift+p / +space).
|
2026-10-16 11:10:43 -04:00
|
|
|
bind -N "Project tasks (pj)" t run-shell "~/.config/tmux/plugins/tmux-palette-plugin/bin/tmux-palette.sh tasks"
|
|
|
|
|
# Session picker (tmux-sessions): other sessions, every window, zoxide and
|
|
|
|
|
# ~/.config dirs, rename/kill; a query that matches nothing becomes a new
|
|
|
|
|
# session in its zoxide match. prefix + o (Ghostty ctrl+shift+o).
|
|
|
|
|
bind -N "Sessions" o run-shell "~/.config/tmux/plugins/tmux-palette-plugin/bin/tmux-palette.sh sessions"
|
2026-10-14 13:32:57 -04:00
|
|
|
|
|
|
|
|
# Direct launchers, the kitty ctrl+shift chords (Ghostty sends prefix + key).
|
|
|
|
|
# Programs are exec'd so the tab icon is the program's.
|
|
|
|
|
bind -N "Yazi in a new window" y new-window -c "#{pane_current_path}" "exec yazi"
|
|
|
|
|
bind -N "Neovim in a new window" e new-window -c "#{pane_current_path}" "exec nvim"
|
|
|
|
|
bind -N "Lazygit popup" g display-popup -E -w 90% -h 90% -d "#{pane_current_path}" lazygit
|
|
|
|
|
# omp in a background session <session>:omp shown in a popup (pj-palette --bg,
|
|
|
|
|
# listed under Running in the tasks palette). Inside any such popup, prefix + a
|
|
|
|
|
# hides it instead, so ctrl+shift+a toggles.
|
|
|
|
|
bind -N "omp popup (toggle)" a if -F "#{@pj_task}" detach-client \
|
|
|
|
|
'run-shell -b "~/.local/bin/pj-palette --bg #{q:session_name}:omp #{q:pane_current_path} omp"'
|
|
|
|
|
|
|
|
|
|
# Persistence: continuum saves every 10 min (resurrect: prefix + C-s save,
|
|
|
|
|
# prefix + C-r restore) and restores the last save when the server starts,
|
|
|
|
|
# i.e. the first Ghostty window after login or reboot.
|
|
|
|
|
set -g @resurrect-capture-pane-contents 'on'
|
|
|
|
|
set -g @continuum-save-interval '10'
|
|
|
|
|
set -g @continuum-restore 'on'
|
|
|
|
|
|
|
|
|
|
# Keep at the very bottom.
|
|
|
|
|
run '~/.config/tmux/plugins/tpm/tpm'
|