| # Options {{{1 |
| # Server {{{2 |
| |
| # Don't pile up more than 10 buffers (down from 50 by default).{{{ |
| # |
| # Rationale: Keeping a tidy stack, with relevant information, could help us |
| # integrate tmux buffers in our workflow more often. |
| # |
| # However, maybe we could keep a big stack of buffers, and filter them by |
| # pressing `f` in the window opened by `choose-buffer`. |
| # Alternatively, we could also try to use fzf to fuzzy search through their |
| # contents... |
| #}}} |
| set -s buffer-limit 10 |
| |
| |
| # What does this option control?{{{ |
| # |
| # It sets the time in milliseconds for which tmux waits after an escape is input |
| # to determine if it is part of a function or meta key sequences. |
| # The default is 500 millisec onds. |
| #}}} |
| # Why do you reset it?{{{ |
| # |
| # The default value introduces lag when we use Vim and escape from insert to |
| # normal mode. We want to reduce the timeout. |
| #}}} |
| # Why don't you set it to 0 ?{{{ |
| # |
| # > Some people set it to zero but I consider that risky if you're connecting |
| # > over a wide-area network or there is anything else that might insert small |
| # > delays between the delivery of chars in such a sequence. |
| # |
| # Source: https://github.com/tmux/tmux/issues/353#issuecomment-294570322 |
| # |
| # Basically, we should still let a few ms to be sure all the keys in a control |
| # sequence will have enough time to reach tmux. |
| #}}} |
| set -s escape-time 10 |
| |
| # If the terminal supports focus events, they will be requested by the tmux |
| # client and passed through to the tmux server, then to the programs it runs. |
| # Necessary to be able to listen to `FocusGained` and `FocusLost`. |
| # Also necessary for `pane-focus-[in|out]` hooks. |
| set -s focus-events on |
| |
| # history of tmux commands (pfx :) |
| set -s history-file "$HOME/.tmux/command_history" |
| |
| # What does this do?{{{ |
| # |
| # It makes tmux sometimes send an OSC 52 sequence – which sets the terminal |
| # clipboard content – if there is an `Ms` entry in the terminfo description of |
| # the outer terminal. |
| #}}} |
| # What are the possible values of this option?{{{ |
| # |
| # - `on` |
| # - `external` |
| # - `off` |
| # |
| # --- |
| # |
| # There are 3 ways to create a tmux buffer: |
| # |
| # 1. invoke the `set-buffer` or `load-buffer` tmux commands |
| # 2. copy text in copy mode (`send -X copy-selection`, `copy-pipe`, ...) |
| # 3. send an OSC 52 sequence from an application inside tmux (e.g. `$ printf ...`) |
| # |
| # `1.` always creates a tmux buffer; never sets the X clipboard. |
| # `2.` always creates a tmux buffer; sets the X clipboard via OSC 52 iff `set-clipboard` is not `off`. |
| # `3.` creates a tmux buffer *and* sets the X clipboard via OSC 52 iff `set-clipboard` is `on`. |
| # |
| # IOW, `external` makes tmux *automatically* set the X clipboard when you yank |
| # sth in copy mode via OSC 52, while `on` does the same, but also allows an |
| # application to *manually* set a tmux buffer/the X clipboard via a OSC 52. |
| # |
| # Source: https://unix.stackexchange.com/a/560464/289772 |
| #}}} |
| # What is the possible drawback of the value `on`?{{{ |
| # |
| # https://github.com/tmux/tmux/wiki/Clipboard#security-concerns |
| #}}} |
| # How to disable OSC 52 for some terminals, but not all?{{{ |
| # |
| # # or use 'external' |
| # set -s set-clipboard on |
| # set -as terminal-overrides 'yourTERMname:Ms@' |
| # ^ |
| # man terminfo /Similar Terminals/;/canceled |
| #}}} |
| set -s set-clipboard external |
| |
| # Why do you move your 'terminal-overrides' settings in another file?{{{ |
| # |
| # It makes it easier to source the settings only when the tmux server is started; |
| # not when we manually re-source `tmux.conf`. |
| #}}} |
| # Why do you need them to be sourced only once?{{{ |
| # |
| # We *append* strings to the value of the 'terminal-overrides' option. |
| # I don't want to append the same strings again and again every time I re-source |
| # `tmux.conf`. |
| #}}} |
| # Why don't you simply reset the option before appending a value?{{{ |
| # |
| # That would make us lose the default value of the option: |
| # |
| # terminal-overrides[0] "xterm*:XT:Ms=\\E]52;%p1%s;%p2%s\\007:Cs=\\E]12;%p1%s\\007:Cr=\\E]112\\007:Ss=\\E[%p1%d q:Se=\\E[2 q" |
| # terminal-overrides[1] screen*:XT |
| # |
| # --- |
| # |
| # Besides, one of the value we append to 'terminal-overrides' depends on the value of `$TERM`. |
| # |
| # if '[ "$TERM" != "st-256color" ]' 'set -as terminal-overrides ",*:Cr=\\E]112\\007"' |
| # ^----------------------^ |
| # |
| # And the value of `$TERM` will be correct only the first time we source `tmux.conf`. |
| #}}} |
| |
| # What's the value of `$TERM` here?{{{ |
| # |
| # The first time our `tmux.conf` is sourced, it matches the `$TERM` of the |
| # outer terminal; the next times, it's the value of 'default-terminal' (i.e. |
| # 'tmux-256color'). |
| #}}} |
| # What's the meaning of this `if` guard?{{{ |
| # |
| # The condition `[ "$TERM" != "tmux-256color" ]` is true only the first time our |
| # `tmux.conf` is sourced. |
| # So, this part of the guard means: “if this is the first time the file is sourced”. |
| # This is equivalent to `has('vim_starting')` in Vim. |
| # |
| # The condition `[ -n "$DISPLAY" ]` is true only in a GUI environment. |
| # So, this part of the guard means: “don't source the file if we are in a console”. |
| # Indeed, I doubt a linux console is able to understand any sequence we might |
| # want to use. |
| #}}} |
| # Could we use `%if` instead of `if`?{{{ |
| # |
| # You could try this: |
| # |
| # %if "#{!=:$TERM,#{default-terminal}}" |
| # source-file "$HOME/.config/tmux/terminal-overrides.conf" |
| # %endif |
| # |
| # But it doesn't seem to work; the guard would not prevent the file from being re-sourced. |
| # I think that's because, in this case, `$TERM` refers to the value in the |
| # environment of the tmux server process; and for the latter, `TERM` always |
| # matches the outer terminal. |
| #}}} |
| if '[ "$TERM" != "#{default-terminal}" -a -n "$DISPLAY" ]' { source "$HOME/.config/tmux/terminal-overrides.conf" } |
| |
| # Leave `default-terminal` at the end.{{{ |
| # |
| # In my limited testing, moving it above would not cause an issue, but better be |
| # safe than sorry. |
| # In particular, I want to be sure that the value of `$TERM` is not |
| # 'tmux-256color' the first time our `tmux.conf` is sourced; otherwise |
| # `terminal-overrides.conf` would never be sourced. |
| #}}} |
| # Why `-s` instead of `-g`? {{{ |
| # |
| # Since tmux 2.1, `default-terminal` is a server option, not a session option. |
| # |
| # > As a side effect this changes default-terminal to be a server rather than a |
| # > session option. |
| # |
| # https://github.com/tmux/tmux/commit/7382ba82c5b366be84ca55c7842426bcf3d1f521 |
| # Confirmed by the fact that `default-terminal` is described in the section of |
| # the server options in the man page. |
| # Also confirmed by the fact that it's listed in the output of: |
| # |
| # tmux show -s |
| # |
| # However, according to nicm: |
| # |
| # > You do not have to use -s or -w for set-option except for user options. |
| # > tmux can work it out from the option name. |
| # > For show-option you do need it. |
| # |
| # So, we could omit `-s`, but I prefer to be explicit. |
| #}}} |
| # Why not let tmux use the default value `screen` (for `$TERM`)?{{{ |
| # |
| # By default, most terminals set `$TERM` to `xterm` because the `xterm` entry is |
| # present and set in the terminfo db of most machines. |
| # tmux sets it to `screen`, again, because it's a popular entry (more than the |
| # `tmux` one). |
| # The `xterm`/`screen` value implies that the terminal will declare supporting |
| # only 8 colors; confirmed by `$ tput colors`. |
| # |
| # Because of this, the theme of some programs might be off (including Vim and |
| # the terminal itself). We want the terminal to declare it supports 256 colors, |
| # which anyway is usually true. |
| #}}} |
| # Do we need `$TERM` to contain `tmux`?{{{ |
| # |
| # Yes. To support italics: |
| # |
| # The `screen-256color` entry in the terminfo db doesn't have a `sitm` field. |
| # IOW, the db reports that screen is unable to support italics, which is true. |
| # So, if we set `$TERM` to `screen-256color`, when an application will want to |
| # make some text appear italicized, it will think it's not possible. |
| # But it *is* possible, because we use tmux, not screen. And tmux *does* |
| # support the italics style. |
| # The solution is to set `$TERM` to `tmux-256color` so that when an application |
| # queries the terminfo db, it finds the field `sitm` with the right value |
| # `\E[3m`. |
| # |
| # See also: |
| # https://github.com/tmux/tmux/wiki/FAQ#i-dont-see-italics-or-italics-and-reverse-are-the-wrong-way-round |
| # https://github.com/tmux/tmux/issues/175#issuecomment-152719805 |
| #}}} |
| # `256color`?{{{ |
| # |
| # For a Vim color scheme to be correctly applied, no. |
| # Because it seems that our current theme automatically sets the number of |
| # colors to 256: |
| # |
| # :runtime colors/seoul256.vim |
| # :echo &t_Co |
| # |
| # But, for the color schemes of other programs, maybe. |
| #}}} |
| set -s default-terminal tmux-256color |
| |
| # Session {{{2 |
| |
| # Don't ring the bell in the current window. |
| set -g bell-action other |
| |
| # Why?{{{ |
| # |
| # If a new window is created without any command to execute, tmux reads the |
| # session option `default-command` to find one. |
| # By default, its value is an empty string which instructs tmux to create a |
| # *login* shell using the value of the default-shell option. |
| # The default value of the latter is `$SHELL` (atm: `/usr/local/bin/zsh`). |
| # |
| # When we create a new window, we want a *non*-login shell, because a login zsh |
| # shell sources `~/.zprofile`, which we use to execute code specific to a |
| # virtual *console* (set the background to white in the console). |
| # This code is not suited to a virtual *terminal*. |
| # |
| # More generally, we don't want a *non*-login shell to source login files |
| # (`.profile`, `.zprofile`, `.zlogin`). |
| # |
| # So, we give the value zsh to `default-command` to prevent tmux from starting a |
| # login shell. |
| #}}} |
| set -g default-command "$SHELL" |
| |
| # Don't detach the client when the current session is killed. |
| set -g detach-on-destroy off |
| |
| # display status line messages and other on-screen indicators for 1s |
| # (or until a key is pressed) |
| set -g display-time 1000 |
| # display the indicators shown by the display-panes command for 5s |
| set -g display-panes-time 5000 |
| |
| # increase scrollback buffer (2000 → 50000) |
| # |
| # `history-limit` has nothing to do with the history of executed tmux commands. |
| # It controls the amount of lines you can scroll back when you enter copy mode. |
| set -g history-limit 50000 |
| |
| # Index options |
| # When we create a new window, tmux looks for an unused index, starting from 0.{{{ |
| # |
| # I prefer 1, because: |
| # |
| # - I usually count from 1, not 0 |
| # - this lets us run `:movew -t :0` to move the current window in first position |
| # |
| # Note that you can't run `:movew -t :1` to move the window in first position, |
| # because 1 is already taken by the first window. |
| # `:movew` expects an index which is “free” (i.e. not used by any existing window). |
| # |
| # Also, note that when running `:movew -t :0`, tmux renumbers all windows |
| # automatically from whatever value is assigned to the 'base-index' option. |
| #}}} |
| set -g base-index 1 |
| # │ |
| # └ must be applied globally to all sessions |
| # |
| # same thing for the panes |
| set -gw pane-base-index 1 |
| # ││ |
| # │└ window option |
| # └ must be applied globally to all windows |
| |
| # make tmux capture the mouse and allow mouse events to be bound as key bindings |
| set -g mouse on |
| |
| # use `M-space` as a prefix |
| set -g prefix M-space |
| |
| # renumber windows, when:{{{ |
| # |
| # - we destroy one of them |
| # - we move the first window (index 1) at the end (index 99), by running `movew -t :99` |
| # |
| # to prevent holes in the sequence of indexes |
| #}}} |
| set -g renumber-windows on |
| |
| # time for repeating of a hotkey bound using the -r flag without having to type the prefix again; default: 500 |
| set -g repeat-time 1000 |
| |
| # Some consoles don't like attempts to set the window title. |
| # This might cause tmux to freeze the terminal when you attach to a session. |
| # https://github.com/tmux/tmux/wiki/FAQ#tmux-freezes-my-terminal-when-i-attach-to-a-session-i-have-to-kill--9-the-shell-it-was-started-from-to-recover |
| set -g set-titles off |
| |
| # update the status line every 5 seconds (instead of 15s by default) |
| set -g status-interval 5 |
| |
| # emacs key bindings in tmux command prompt (prefix + :) are better than vi keys, |
| # even for vim users. |
| set -g status-keys emacs |
| |
| # color of status line |
| set -g -F status-style "bg=#{?#{==:$DISPLAY,},blue,colour138}" |
| |
| # Center the position of the window list component of the status line |
| set -g status-justify centre |
| |
| # cache the number of cpu cores in `~/.ncore`, which a shell command in 'status-right' is going to need |
| if '[ ! -s "${HOME}/.ncore" ]' \ |
| { run "lscpu | awk '/^CPU\\(s\\):\\s*[0-9]/ { print $2 }' >\"${HOME}/.ncore\"" } |
| |
| # set the contents of the status line |
| # What's `#[...]`?{{{ |
| # |
| # It lets you embed some styles. |
| # If you want to apply the same style all over the left part or the right part |
| # of the status line, you can also use `status-left-style` or `status-right-style`: |
| # |
| # set -g status-left '#[fg=colour15,bold] #S' |
| # ⇔ |
| # set -g status-left ' #S' |
| # set -g status-left-style '#[fg=colour15,bold]' |
| # |
| # However, I prefer embedding the styles inside the value of `status-left` and |
| # `status-right`, because: |
| # |
| # - it's more concise |
| # - it's more powerful: you can set the style of an arbitrary *portion* of the status line |
| # |
| # --- |
| # |
| # Note that you can use this syntax only in the value of an option which sets |
| # the *contents* of sth, not its style. |
| # So, this is *not* a valid syntax: |
| # |
| # # ✘ |
| # set -g status-left-style '#[fg=colour15,bold]' |
| # |
| # Here, you must get rid of `#[...]`: |
| # |
| # # ✔ |
| # set -g status-left-style 'fg=colour15,bold' |
| #}}} |
| # What's `#{?...}`?{{{ |
| # |
| # A conditional: |
| # |
| # #{?test,val1,val2} |
| # |
| # For example: |
| # |
| # {?client_prefix,#[bold],} |
| # |
| # This will be evaluated into the style `bold` if the prefix has been pressed, |
| # or nothing otherwise. |
| #}}} |
| # Why do you use `nobold`?{{{ |
| # |
| # We set the style `bold` for some part of the status line. |
| # But a style applies to *all* the remaining text in the status line. |
| # We need `nobold` to reset the style. |
| #}}} |
| # How can I include the time of the day or the hour in the status line?{{{ |
| # |
| # Use `date(1)` and `%` items: |
| # |
| # %a = day of week |
| # %d = day of month |
| # %b = month |
| # %R = hour |
| # |
| # See `man date`. |
| #}}} |
| set -g status-left ' #[fg=colour7]#{?client_prefix,#[fg=colour0],}#S#[fg=colour7]' |
| |
| # Which alternative could I use to get the cpu load?{{{ |
| # |
| # $ uptime|awk '{split(substr($0, index($0, "load")), a, ":"); print a[2]}' |
| # |
| # https://github.com/tmux/tmux/wiki/FAQ#what-is-the-best-way-to-display-the-load-average-why-no-l |
| #}}} |
| # Why don't you write the code in a script and invoke it with `#(my-script.sh)`?{{{ |
| # |
| # A script needs another shell to be interpreted. |
| # The latter adds overhead, which would almost double the time the code needs to |
| # be run. |
| # |
| # Check this: |
| # |
| # $ cat <<'EOF' >/tmp/sh.sh |
| # awk 'BEGIN { getline load <"/proc/loadavg"; getline ncore <(ENVIRON["HOME"]"/.ncore"); printf("%d", 100 * load / ncore) }' |
| # EOF |
| # $ chmod +x /tmp/sh.sh |
| # |
| # $ time zsh -c 'repeat 100 /tmp/sh.sh' |
| # ... 0,420 total˜ |
| # $ time zsh -c 'repeat 100 awk '\''BEGIN { getline load <"/proc/loadavg"; getline ncore <(ENVIRON["HOME"]"/.ncore"); printf("%d", 100 * load / ncore) }'\''' |
| # ... 0,193 total˜ |
| #}}} |
| # Why do you double the percent sign in `printf("%%d", ...)`?{{{ |
| # |
| # The code is going to be expanded inside the value of 'status-right'. |
| # And the latter is always passed to `strftime(3)` for which the percent sign |
| # has a special meaning. |
| # As a result, if you don't double the percent sign – to make it literal – `%d` |
| # will be replaced with the current day of the month (01, 02, ..., 31). |
| # |
| # From `man tmux /status-right`: |
| # |
| # > As with status-left, string will be passed to strftime(3) and character |
| # > pairs are replaced. |
| #}}} |
| cpu='awk '\''BEGIN { \ |
| getline load <"/proc/loadavg"; \ |
| getline ncore <(ENVIRON["HOME"]"/.ncore"); \ |
| printf("%%d", 100 * load / ncore) }'\''' |
| |
| mem='free | awk '\''/Mem/ { total = $2; used = $3 }; \ |
| END { printf("%%d", 100 * used / total) }'\''' |
| # TODO: Maybe we could get rid of `free(1)`, by inspecting `/proc/meminfo`. |
| # However, I can find the second field ("total") of `free(1)` in this file |
| # (first line: "MemTotal"), but not the third one ("used"). |
| # |
| # Update: From `man free`: |
| # |
| # used Used memory (calculated as total - free - buffers - cache) |
| # |
| # You would have to read 4 files to compute the `used` field. |
| # Make some tests (with `time(1)`) to see whether the resulting `awk(1)` command |
| # is slower than what we currently run. |
| |
| # TODO: Color the numbers in red if they exceed some threshold. |
| # Try to capture the output of the 2 shell commands in tmux variables, so that |
| # we can test them in a conditional. |
| set -g status-right '#[fg=colour235]M #[fg=colour15,bold]' |
| set -ga status-right "#($mem) " |
| set -ga status-right '#[fg=colour235,nobold]C #[fg=colour15,bold]' |
| set -ga status-right "#($cpu) " |
| |
| setenv -gu cpu |
| setenv -gu mem |
| |
| # Why do you want `COLORTERM` to be automatically updated?{{{ |
| # |
| # It can be useful to detect a terminal which lies about its identity. |
| # E.g., xfce4-terminal advertises itself as `xterm-256color`, but the value of |
| # its `COLORTERM` variable is 'xfce4-terminal'. |
| # |
| # So, the more reliable `COLORTERM` is, the better we can detect that we're in |
| # xfce4-terminal, and react appropriately. |
| # This can be useful, for example, to prevent vim-term from sending `CSI 2 SPC q` |
| # when we're leaving Vim from xfce4-terminal on Ubuntu 16.04. |
| # The latter doesn't understand this sequence, and once sent to the terminal, |
| # tmux will regularly reprint the sequence wherever our cursor is. |
| #}}} |
| # Why could I be tempted to run the same command for `LESSOPEN`, `LESSCLOSE`, `LS_COLORS`?{{{ |
| # |
| # setenv -gu LESSOPEN |
| # setenv -gu LESSCLOSE |
| # setenv -gu LS_COLORS |
| # |
| # These environment variables are set in `~/.zshenv`, but only on the condition |
| # they've not been set yet. |
| # The purpose of the condition is to make the shell quicker to start. |
| # Indeed, setting these variables adds around 8ms to the shell's startup time. |
| # However, if they are set in the tmux global environment, then they'll never be |
| # reset when we start a new shell, because the condition will never be |
| # satisfied. |
| # |
| # This means that we can't change the value of these variables by simply editing |
| # `~/.zshenv`, which can be an issue. |
| #}}} |
| # Why don't you do it?{{{ |
| # |
| # Because it would add around 8ms to the startup time of every shell we ask tmux |
| # to open. |
| #}}} |
| # Isn't this an issue?{{{ |
| # |
| # No. |
| # If you want to modify one of these variables, and if you want the change to be |
| # applied immediately without restarting the tmux server, do it in the context |
| # of the tmux global environment: |
| # |
| # $ tmux setenv -g LESSOPEN new_value |
| #}}} |
| if '[ "$TERM" != "#{default-terminal}" ]' { set -ga update-environment COLORTERM } |
| # TODO: We have several similar `if` conditions. Maybe we should write only one, |
| # and put inside all the commands we want to run. |
| # This would make tmux start a little faster. |
| |
| # display a message when activity is detected in a window |
| # Why?{{{ |
| # |
| # We haven't customized the status line to include an indicator when some |
| # activity is detected in a window, because by default we don't monitor activity |
| # ('monitor-activity' is off). |
| # Indeed, generally, most windows will produce some output and have some activity. |
| # Seeing a lot of indicators in the status line, all the time, is meaningless. |
| # |
| # But we do have a key binding to temporarily toggle 'monitor-activity' in the |
| # current window; so, we need a way to be notified when some activity is later |
| # detected in it. |
| #}}} |
| set -g visual-activity on |
| |
| # Window {{{2 |
| |
| # Use vi key bindings in copy mode. |
| set -gw mode-keys vi |
| |
| # colors of *borders* of focused and non-focused panes |
| set -gw pane-active-border-style 'fg=colour138,bg=#cacaca' |
| set -gw pane-border-style 'fg=colour138,bg=#cacaca' |
| |
| # How to insert the index of a window?{{{ |
| # |
| # Use the alias `#I`. |
| #}}} |
| # How to insert its flags, like `Z` for a zoomed window?{{{ |
| # |
| # Use the alias `#F`. |
| #}}} |
| # Set what to display for the current window (then for the other ones), and how, |
| # in the status line window list. |
| # See: https://github.com/tmux/tmux/issues/74#issuecomment-129130023 |
| set -gw window-status-current-format '#[fg=colour232,bg=colour253]#W#F#{?window_zoomed_flag, #[fg=blue]#P/#{window_panes},}' |
| set -gw window-status-format '#[fg=colour232#,bg=colour248]#W#F#[bg=default]' |
| |
| # Pane {{{2 |
| |
| # colors of focused and non-focused panes |
| # Because we use `-gw`, the colors will affect any pane.{{{ |
| # |
| # But, at runtime, you could use `-p` to set the color of a given pane when it's |
| # (un)focused differently. |
| #}}} |
| set -gw window-active-style 'bg=#dbd6d1' |
| set -gw window-style 'bg=#cacaca' |
| # }}}1 |
| # Key Bindings {{{1 |
| # root {{{2 |
| |
| # `F1`, ..., `F10` are used in `htop(1)`. |
| # `F11` and `F12` are used in WeeChat to scroll in the nicklist bar. |
| bind -T root S-F8 run -b 'tmux_log' |
| |
| # Why do you rebind `command-prompt` to `pfx + ;`? It's already bound to `pfx + :`...{{{ |
| # |
| # We often press the prefix key by accident, then press `:` to open Vim's |
| # command-line. As a result, we open tmux command-line; it's distracting. |
| #}}} |
| bind ';' command-prompt |
| unbind : |
| # Do *not* bind `command-prompt` to `M-:`; we press it by accident too frequently. |
| |
| # focus next/previous window |
| # I'm frequently pressing these key bindings in Vim's insert mode. It's distracting!{{{ |
| # |
| # Idea1: Use `pfx w` to focus an arbitrary window, and supercharge `pfx h/l` to |
| # focus the next/previous pane or window. |
| # |
| # bind -r h if -F '#{pane_at_left}' 'prev' 'selectp -L' |
| # bind -r l if -F '#{pane_at_right}' 'next' 'selectp -R' |
| # |
| # Idea2: Use `M-h/l` twice when the current pane is running Vim. |
| # |
| # set -g @foo 0 |
| # bind -T root M-l if -F '#{m:*vim,#{pane_current_command}}' { if -F '#{@foo}' { set -g @foo 0 ; next } { set -g @foo 1 } } { next } |
| # bind -T root M-h if -F '#{m:*vim,#{pane_current_command}}' { if -F '#{@foo}' { set -g @foo 0 ; prev } { set -g @foo 1 } } { prev } |
| # |
| # Idea3: Make tmux send `M-h` or `M-l` when Vim is running in the current pane, |
| # and let Vim decide what to do, based on whether we're in insert mode or normal |
| # mode. |
| # |
| # noremap! <expr> <m-h> <sid>m_hl('h') |
| # noremap! <expr> <m-l> <sid>m_hl('l') |
| # nno <expr> <m-h> <sid>m_hl('h') |
| # nno <expr> <m-l> <sid>m_hl('l') |
| # xno <expr> <m-h> <sid>m_hl('h') |
| # xno <expr> <m-l> <sid>m_hl('l') |
| # |
| # fu s:m_hl(seq) abort |
| # if mode() =~# '^[ic]$' |
| # return '' |
| # endif |
| # call system('tmux '..(a:seq is# 'l' ? 'next' : 'prev')) |
| # return '' |
| # endfu |
| # bind -T root M-l if -F '#{m:*vim,#{pane_current_command}}' 'send M-l' 'next' |
| # bind -T root M-h if -F '#{m:*vim,#{pane_current_command}}' 'send M-h' 'prev' |
| # |
| # Problem: When we're running Vim without config (`-Nu NONE`), we can't focus another tmux window. |
| # This is because those custom mappings are not installed then. |
| #}}} |
| # Why do you inspect these window flags?{{{ |
| # |
| # To prevent the commands from wrapping around the edges. |
| # They do that by default, and that bothers me at the moment. |
| # |
| # For example, I often want to focus the next window, and press `¹` by accident |
| # instead of `²`. It still works, because we only have 2 windows, but that |
| # prevents me from learning to press the correct keys. |
| # |
| # Once you're confident that those keys are well-chosen, and they've passed into |
| # your muscle memory, you could just install: |
| # |
| # bind -T root ¹ prev |
| # bind -T root ² next |
| #}}} |
| bind -T root ¹ if -F '#{window_start_flag}' {} { prev } |
| bind -T root ² if -F '#{window_end_flag}' {} { next } |
| # TODO: Sometimes, we press these keys by accident. Find better ones?{{{ |
| # |
| # In particular, when we press `[ox` (where `x` is some character) to toggle |
| # some Vim setting, we need to press `AltGr` to produce `[`; but sometimes, we |
| # don't release the key before pressing `o`. |
| # As a result, we press `AltGr + o` which produce `²`; tmux intercepts the |
| # keypress, and tries to visit the previous window, while in reality, we wanted |
| # to toggle some Vim setting. |
| #}}} |
| |
| # `M-s` to enter copy mode |
| # Do *not* bind `M-s` to anything while in copy mode!{{{ |
| # |
| # This would make you lose an interesting feature of the `copy-mode` command |
| # while you're already in copy mode, reading the output of some command such as |
| # `list-keys`. |
| # |
| # The default behavior makes tmux show you the contents of the original window: |
| # |
| # # you're reading a file |
| # :list-keys |
| # :copy-mode |
| # # the window shows again the file you were reading |
| # # press `q`, and you get back the output of `:list-keys` |
| # |
| # I don't know where this is documented. |
| # And I don't know why the `copy-mode` command is invoked when we press `M-s` |
| # while in copy mode. |
| # We only have one key binding using `M-s` as its lhs, and it's in the root |
| # table, not in the copy-mode table. |
| # |
| # Note that this feature is not specific to our `M-s` key binding. |
| # I can reproduce with no config (and `C-b [` instead of `M-s`). |
| #}}} |
| bind -T root M-s copy-mode |
| |
| bind -T root M-z resize-pane -Z |
| bind -T root M-Z lastp \; resize-pane -Z |
| |
| # Do *not* exit copy mode when when we reach the bottom of the scrollback buffer.{{{ |
| # |
| # We remove the `-e` flag which is passed to `copy-mode` by default, so that if |
| # we enter copy mode by scrolling the mouse wheel upward, and we press a key |
| # which reaches the bottom of the scrollback buffer, we don't quit copy mode. |
| # |
| # If you leave `-e` in the default key binding, here's what could happen: |
| # you scroll the wheel upward to enter copy mode; at one point, you keep |
| # pressing `C-d` to scroll toward the bottom; once you reach the bottom, you'll |
| # quit copy mode, and `C-d` will close the shell if the command-line is empty. |
| #}}} |
| # Where did you find the code?{{{ |
| # |
| # $ tmux -Lx -f/dev/null start \; lsk | grep 'root.*WheelUpPane' |
| #}}} |
| bind -T root WheelUpPane \ |
| if -F -t= '#{mouse_any_flag}' \ |
| { send -M } \ |
| { if -Ft= '#{pane_in_mode}' 'send -M' 'copy-mode -t=' } |
| |
| # But *do* exit copy mode if we scroll downward with the mouse wheel and reach the bottom of the buffer. |
| bind -T copy-mode-vi WheelDownPane \ |
| selectp \; \ |
| send -X -N 5 scroll-down \; \ |
| if -F '#{scroll_position}' '' 'send -X cancel' |
| |
| # copy-mode-vi {{{2 |
| |
| bind -T copy-mode-vi C-Space send -X set-mark |
| # actually, it should be named "exchange-point-and-mark"... |
| bind -T copy-mode-vi C-x send -X jump-to-mark |
| |
| # jump Back to the Beginning of the previous shell command{{{ |
| # |
| # Look for the previous shell prompt, to get to the beginning of the last |
| # command output. After pressing the key binding, you can visit all the other |
| # prompts by pressing `n` or `N`. |
| # |
| # Inspiration: https://www.youtube.com/watch?v=uglorjY0Ntg |
| #}}} |
| bind -T copy-mode-vi ! send -X start-of-line \; send -X search-backward '٪' |
| |
| # Make tmux use the prefix 'buf_' instead of 'buffer' when naming the buffer |
| # storing the copied selection. |
| bind -T copy-mode-vi Enter send -X copy-selection-and-cancel 'buf_' |
| |
| bind -T copy-mode-vi g switchc -T g-prefix |
| bind -T g-prefix g send -X history-top |
| # Open a visually selected text (filepath or url) by pressing `gf` or `gx`. |
| # TODO: The key binding will break if the file name contains double quotes.{{{ |
| # |
| # Find a way to escape special characters. |
| # |
| # I tried `$ tmux display -p '#{q:#(tmux pasteb)}'`, but it doesn't work. |
| # You probably need `q:`, but a modifier needs to be followed by the name of a |
| # replacement variable and `#(tmux pasteb)` is not one. |
| # |
| # Update: |
| # I don't think you should use `q:`. |
| # Maybe you should try to find a shell utility which quotes special characters |
| # in a given text. |
| # Does such a tool exist? |
| # If it does, maybe you could try: `#(magic_tool $(tmux pasteb))`. |
| # |
| # Update: |
| # `#{}` can be used for a user option (`@foo`). |
| # You could temporarily set a user option with the the filepath/url, and quote |
| # it with `q:`. |
| # |
| # Make some tests on that: |
| # |
| # http://example.org/foo/bar"baz.html |
| # http://example.org/foo/bar'baz.html |
| # https://www.reddit.com/r/Fantasy/ |
| # |
| # bind -T copy-mode-vi x send -X copy-selection-and-cancel \; \ |
| # run 'tmux set @copied_url "$(tmux showb)"' \; \ |
| # run 'xdg-open "#{q:@copied_url}"' |
| # |
| # For some reason, we need 2 `run-shell`, otherwise, it seems that the key |
| # binding doesn't update the url, when we try a new one. |
| # |
| # For some reason, when we try to open this: |
| # |
| # http://example.org/a'b.html |
| # |
| # tmux opens this instead: |
| # |
| # http://example.org/a/'b.html |
| # ^ |
| # ✘ |
| # |
| # For some reason, when we try to open this: |
| # |
| # http://example.org/a"b.html |
| # |
| # tmux doesn't escape the double quote. |
| # |
| # Text ended before matching quote was found for ".˜ |
| # (The text was '/usr/bin/firefox "http://example.org/foo/bar"baz.html"')˜ |
| # |
| # $ tmux set @foo "a'b" \; display -p '#{q:@foo}' |
| # a\'b˜ |
| # |
| # $ tmux set @foo "a'b" \; run 'echo #{q:@foo}' |
| # a'b˜ |
| # |
| # Why doesn't `run-shell` expand the `#{q:}` format? |
| # |
| # Update: you need to quote the format: |
| # |
| # $ tmux set @foo "a'b" \; run 'echo "#{q:@foo}"' |
| # a\'b˜ |
| # |
| # Update: |
| # I don't think it's possible. |
| # Try to open the urls via `urlscan(1)`. |
| # The latter fails for the first urls. |
| # If a specialized tool fails, I doubt we can do better. |
| #}}} |
| # FIXME: `gf` fails to open a file path starting with `~/`. |
| bind -T g-prefix f send -X pipe "xargs -I {} tmux run 'xdg-open \"{}\"'" |
| bind -T g-prefix x send -X pipe "xargs -I {} tmux run 'xdg-open \"{}\"'" |
| |
| bind -T copy-mode-vi v if -F '#{selection_present}' { send -X clear-selection } { send -X begin-selection } |
| bind -T copy-mode-vi V if -F '#{selection_present}' { send -X clear-selection } { send -X select-line } |
| bind -T copy-mode-vi C-v if -F '#{selection_present}' \ |
| { if -F '#{rectangle_toggle}' \ |
| { send -X rectangle-toggle ; send -X clear-selection } \ |
| { send -X rectangle-toggle } \ |
| } { send -X begin-selection ; send -X rectangle-toggle } |
| |
| # set -s copy-command 'xsel -i' |
| # if there's a selection, make `y` yank it |
| # without selection, make `yy` yank the current line and `yiw` the current word |
| bind -T copy-mode-vi y if -F '#{selection_present}' \ |
| { send -X copy-pipe-and-cancel 'xsel -i -b' 'buf_' } \ |
| { switchc -T operator-pending-and-cancel } |
| # y**y** |
| bind -T operator-pending-and-cancel y send -X copy-line 'buf_' |
| # y**iw** |
| bind -T operator-pending-and-cancel i switchc -T text-object-and-cancel |
| # Do *not* use `select-word`: https://github.com/tmux/tmux/issues/2126 |
| bind -T text-object-and-cancel w \ |
| { send -X cursor-right |
| send -X previous-word |
| send -X begin-selection |
| send -X next-word-end |
| send -X copy-selection-and-cancel 'buf_' } |
| |
| bind -T copy-mode-vi . run "zsh -c \"tmux source =(sed -n '/^# #{@dot_command}/,/^$/p' $HOME/.config/tmux/repeat.conf)\"" |
| |
| # **"A**yiw **"A**yy v_**"A**y |
| bind -T copy-mode-vi '"' switchc -T specify-register |
| bind -T specify-register A switchc -T operate-on-register |
| # Why the `if 'tmux showb'`?{{{ |
| # |
| # `append-selection` doesn't accept the optional prefix buffer name argument. |
| # If there's no buffer, `append-selection` will create a buffer with the prefix |
| # name `buffer`; we want the prefix `buf_`. |
| #}}} |
| # "A**y**iw "A**y**y v_"A**y** |
| bind -T operate-on-register y if -F '#{selection_present}' \ |
| { if 'tmux showb' { send -X append-selection } { send -X copy-selection 'buf_' }} \ |
| { switchc -T operator-pending } |
| # "Ay**i**w |
| bind -T operator-pending i switchc -T text-object |
| # "Ay**y** |
| bind -T operator-pending y { run "zsh -c \"tmux source =(sed -n '/^# yy/,/^$/p' $HOME/.config/tmux/repeat.conf)\"" } |
| |
| # v**i**w |
| bind -T copy-mode-vi i switchc -T text-object |
| # Why pass a second command to the first `if`?{{{ |
| # |
| # To support `"Ayiw`. |
| #}}} |
| # vi**w** "Ayi**w** |
| bind -T text-object w if -F '#{selection_present}' \ |
| { send -X stop-selection |
| send -X cursor-right |
| send -X previous-word |
| send -X begin-selection |
| send -X next-word-end } \ |
| { run "zsh -c \"tmux source =(sed -n '/^# yiw/,/^$/p' $HOME/.config/tmux/repeat.conf)\"" } |
| |
| bind -T copy-mode-vi Y send -X copy-end-of-line 'buf_' |
| |
| # Why don't you pass `-b` to run?{{{ |
| # |
| # There's no need to. |
| # `pipe-and-cancel` doesn't block. |
| # The shell command passed as an argument is forked. |
| #}}} |
| bind -T copy-mode-vi S send -X pipe-and-cancel \ |
| "xargs -I {} tmux run 'xdg-open \"https://www.startpage.com/do/dsearch?query={}\"'" |
| |
| # Why? {{{ |
| # |
| # `search-backward-incremental` is better than `search-forward`, because it |
| # highlights all the matches as you type (like in Vim when 'hlsearch' and |
| # 'incsearch' are both set); you need to pass `-i` to `command-prompt` for it to work. |
| # |
| # Also, these key bindings make the prompt less noisy (`/` is shorter than `search down`). |
| # |
| # Inspired from the default emacs key bindings in copy mode. |
| # }}} |
| bind -T copy-mode-vi / command-prompt -ip '/' { send -X search-forward-incremental '%%' } |
| bind -T copy-mode-vi ? command-prompt -ip '?' { send -X search-backward-incremental '%%' } |
| |
| bind -T copy-mode-vi % send -X next-matching-bracket |
| bind -T copy-mode-vi _ send -X start-of-line |
| |
| # move current window position forward/backward |
| # Why not using `h` and `l`, or `j` and `k`?{{{ |
| # |
| # `M-C-[jk]` could be more useful for something else (WeeChat?), and doesn't |
| # match a horizontal motion. |
| # `M-C-[hl]` conflicts with our window manager (move virtual desktop). |
| # `M-C-[HL]` is hard to press. |
| #}}} |
| bind -r > if -F '#{window_end_flag}' { movew -t :0 } { swapw -t :+ ; selectw -n } |
| bind -r < if -F '#{window_start_flag}' { movew -t :99 } { swapw -t :- ; selectw -p } |
| # If you don't like `:99`, you could write this instead:{{{ |
| # |
| # bind -r < if -F '#{window_start_flag}' \ |
| # { run 'tmux movew -t:$((#{W:#{?window_end_flag,#I,}}+1))' } \ |
| # { swapw -t :- ; selectw -p } |
| #}}} |
| |
| # prefix {{{2 |
| |
| # NOTE: `C-\` is free. |
| |
| # cycle through predefined layouts |
| # Do *not* pass `-r` to `bind`!{{{ |
| # |
| # We use Space in Vim as a prefix key. |
| # If you use `-r` here, when you're in Vim there's a risk that when you press |
| # Space as a prefix key, it's consumed by tmux to run `nextl` instead. |
| #}}} |
| bind Space nextl |
| |
| # focus last pane, without breaking the zoomed state of the window |
| bind M-Space lastp -Z |
| |
| # display short description for the next keypress (inspired from a default key binding) |
| bind M-h command-prompt -k -p key { lsk -1N '%%' } |
| # ├┘ ├─┘{{{ |
| # │ └ -N instead show keys and attached notes in the root and prefix key tables; |
| # │ with -1 only the first matching key and note is shown |
| # └ -k is like -1 but the key press is translated to a key name |
| #}}} |
| # What is the side effect of `-1`?{{{ |
| # |
| # Not only does it limit the output of the command to the first matching key and |
| # note, but it also redirects where it's displayed. |
| # Without `-1`, it's displayed on the terminal in copy-mode. |
| # With `-1`, it's displayed as a message on the tmux status line. |
| #}}} |
| |
| # display short description for the next 2 keypresses |
| bind M-l command-prompt -1p 'key1,key2' \ |
| { run "tmux lsk | awk '/-T prefix\s+%1\s+/ { print \$NF }' | xargs -I {} tmux lsk -1N -P 'M-Space %1 ' -T {} '%2'" } |
| |
| # repeat last shell command in last active pane |
| # Warning: This overrides a default key binding:{{{ |
| # |
| # bind-key -T prefix . command-prompt -T target "move-window -t '%%'" |
| #}}} |
| bind . if -F -t '{last}' '#{m:*sh,#{pane_current_command}}' { send -t '{last}' Up Enter } |
| |
| # copy clipboard selection into tmux buffer |
| # Why `run`?{{{ |
| # |
| # To make the shell evaluate the command substitution. |
| #}}} |
| # `--`?{{{ |
| # |
| # The evaluation of the substitution command could start with a hyphen. |
| # And if that happens, tmux could parse the text as an option passed to |
| # `set-buffer` (i.e. `-a`, `-b`, or `-n`). |
| #}}} |
| # `-o`?{{{ |
| # |
| # To make `xsel(1x)` output something. |
| #}}} |
| bind b switchc -T buffer |
| bind -T buffer -N 'copy clipboard into tmux buffer' > run 'tmux setb -- "$(xsel -ob)"' \; display 'clipboard copied into tmux buffer' |
| bind -T buffer -N 'copy tmux buffer into clipboard' < choose-buffer -F '#{buffer_sample}' \ |
| { run 'tmux showb -b "%%" | xsel -ib' ; display 'tmux buffer copied into clipboard' } |
| |
| # We use `*` instead of `q` because it's more consistent with `#`. |
| # They both show information. Besides, if I hit `pfx q` by accident (which |
| # happens often), I won't be distracted by the panes numbers. |
| |
| bind * displayp |
| |
| bind ! show-messages |
| |
| # make these default key bindings repeatable |
| # Do *not* pass `-r` to `bind`!{{{ |
| # |
| # Suppose you're in the 'study' session, Vim is running, and the Vim window is |
| # horizontally split in 2 viewports. |
| # You press `pfx + )` to switch to the 'fun' session, then you press `)` to go |
| # back to the 'study' session. |
| # Finally you press C-j to focus the Vim split below; it won't work because of this: |
| # |
| # bind -r C-j resizep -D 5 |
| #}}} |
| bind ( switchc -p |
| bind ) switchc -n |
| |
| # Problem: tmux-fingers doesn't let us search outside the current screen. |
| # Solution: Install a key binding which lets us search through the scrollback buffer, in a Vim buffer. |
| # What does `-J` do for `capture-pane`?{{{ |
| # |
| # It joins wrapped lines. |
| # |
| # Suppose that we have a long line in a file, which doesn't fit on a single line |
| # of the terminal, but on two. |
| # If you run `$ cat file`, this too-long line will be displayed on two |
| # consecutive lines of the terminal. |
| # Without `-J`, tmux would copy – in one of its buffers – the two consecutive |
| # lines on two different lines. |
| # |
| # But that's not what we want. |
| # We want the buffer to join back these two lines, as they were originally in the file. |
| #}}} |
| # `-S -`? {{{ |
| # |
| # `-S` specifies the starting line number, from where to copy. |
| # The special value `-` refers to the start of the history. |
| # Without this, `capture-pane` would capture only from the first visible line in |
| # the pane; we want the *whole* scrollback buffer. |
| #}}} |
| # Why don't you use `split-window` instead of `popup`?{{{ |
| # |
| # With `split-window`, you would also need to run `resize-pane -Z`. |
| # But what if there's already a zoomed pane in the current window? |
| # After quitting Vim, the latter would be unzoomed. |
| # So, we use `popup` to preserve a possible zoomed pane in the current window. |
| #}}} |
| bind -T root M-c if -F \ |
| '#{||:#{m:*vim,#{pane_current_command}},#{==:#{pane_current_command},man}}' \ |
| '' \ |
| { capture-pane -b scrollback -J -S - |
| popup -E -xC -yC -w75% -h75% -d '#{pane_current_path}' \ |
| "tmux showb -b scrollback | vim --not-a-term +'call tmux#formatCapture#main()' - ; \ |
| tmux deleteb -b scrollback" } |
| |
| # split window vertically / horizontally |
| # When the window is zoomed, we often forget that it's already split, and wrongly press `pfx _`.{{{ |
| # |
| # Make `pfx _` smarter; i.e. if the window is already split and zoomed, don't |
| # split it again, instead focus the previous pane. |
| #}}} |
| bind _ if -F '#{window_zoomed_flag}' 'lastp' 'split-window -v -c "#{pane_current_path}"' |
| bind | if -F '#{window_zoomed_flag}' 'lastp' 'split-window -h -c "#{pane_current_path}"' |
| bind - if -F '#{window_zoomed_flag}' 'lastp' 'split-window -fv -c "#{pane_current_path}"' |
| bind '\' if -F '#{window_zoomed_flag}' 'lastp' 'split-window -fh -c "#{pane_current_path}"' |
| # ├───────────────────────┘ |
| # └ keep current working directory |
| |
| bind -N 'bring arbitrary pane in current window' [ command-prompt -p 'join pane from:' { join-pane -s '%%' } |
| bind -N 'send current pane in arbitrary window' ] command-prompt -p 'send pane to:' { join-pane -t '%%' } |
| bind T breakp |
| |
| # Why a space before every shell command (` cmus`, ` weechat`, ...)?{{{ |
| # |
| # It's useful to prevent zsh from saving the command in the history when we |
| # cancel the search with `C-c` or `C-d` (`setopt HIST_IGNORE_SPACE`). |
| #}}} |
| # What's the `-n` option passed to `neww`?{{{ |
| # |
| # It sets the name of the window. |
| #}}} |
| # What about the `-c` option?{{{ |
| # |
| # It sets the cwd of the shell. |
| #}}} |
| bind M-1 rename -t 0 fun \; \ |
| renamew -t 1 music \; \ |
| send ' cmus' 'Enter' '2' 'Enter' 'Enter' \; \ |
| neww -n irc -c $HOME \; \ |
| send ' weechat' 'Enter' \; \ |
| new -s study \; \ |
| send ' nv' 'Enter' |
| |
| # you need the display command to force tmux to clear the log (`set message-limit 0` is not enough) |
| bind C set -F @message-limit-save '#{message-limit}' \; \ |
| set message-limit 0 \; display 'message log cleared' \; \ |
| set -F message-limit '#{@message-limit-save}' \; set -u @message-limit-save |
| |
| # Note that `clear-history` doesn't clear *all* the history.{{{ |
| # |
| # The last lines of the scrollback buffer which fits in one screen are preserved. |
| # So, if you enter copy mode, you'll still be able to scroll back *some* lines, |
| # but not more than a few dozens. |
| #}}} |
| # We can't use `C-l` for the lhs, because we already use it in another key binding:{{{ |
| # |
| # bind -r C-l resizep -R 5 |
| #}}} |
| bind C-c send C-l \; clear-history |
| # │ │ |
| # │ └ clear tmux scrollback buffer |
| # └ clear terminal screen |
| |
| # Do *not* use `q` in the prefix table; pressed by accident too frequently. |
| bind Q switchc -T Q-prefix |
| bind -T Q-prefix q confirm 'kill-pane' |
| bind -T Q-prefix Q confirm 'kill-window' |
| |
| # Why `TERM="#{client_termname}"` in the 'sourced files' entry?{{{ |
| # |
| # When `$ tmux -v -Ldm` is started, it inherits the TERM of the current tmux |
| # server, which is set by 'default-terminal'. |
| # As a result, the condition `[ if '[ "$TERM" != #{default-terminal} ]'` is |
| # true, and several files which we expect to be sourced, won't be sourced. |
| # |
| # We want to see all files which would be sourced if we were to start tmux from |
| # a regular shell; so we need to reset TERM. |
| #}}} |
| # Why don't you pass `-v` to `show-options`?{{{ |
| # |
| # If you do, it would considerably simplify our commands; we wouldn't need `sed(1)` at all. |
| # Unfortunately, it would also fuck up the output if one of the alias is defined |
| # on several lines. |
| # |
| # By avoiding `-v`, we make sure that each alias is output on a single line. |
| #}}} |
| bind i display-menu -x 0 -y 0 \ |
| 'server information' i info \ |
| 'key bindings' K lsk \ |
| 'aliases' a { run 'tmux show -s command-alias | column -t -s= | sed "s/^command-alias\[[0-9]*]\s*//; s/^\"\|\"$//g"' } \ |
| 'sourced files' f { run 'cd "$(mktemp -d /tmp/.tmux.XXXXXXXXXX)" \ |
| ; TERM="#{client_termname}" tmux -v -Ldm start \ |
| ; grep loading tmux-server*.log | grep -v grep | sed "s/.*loading \(.*\)/\1/"' } \ |
| '' \ |
| 'options' o { display-menu -y 0 \ |
| 'server options' C-s { show -s } \ |
| 'global session options' s { show -g } \ |
| 'local session options' S { show } \ |
| '' \ |
| 'global window options' w { show -gw } \ |
| 'local window options' W { show -w } \ |
| 'local pane options' p { show -p } \ |
| } \ |
| '' \ |
| 'global hooks' h { show-hooks -g } \ |
| 'local hooks' H { show-hooks } \ |
| '' \ |
| 'global environment' e { showenv -g } \ |
| 'local environment' E { showenv } \ |
| '' \ |
| 'outer terminfo description' t { run 'infocmp -1x "#{client_termname}" | sort' } \ |
| 'inner terminfo description' T { run 'infocmp -1x "#{default-terminal}" | sort' } \ |
| '' \ |
| 'default settings' d { display-menu -y 0 \ |
| 'key bindings' K { run 'tmux -Ldm -f/dev/null start \; lsk' } \ |
| 'aliases' a { run 'tmux -Ldm -f/dev/null start \; show -sv command-alias | column -t -s= | sed "s/^command-alias\\[[0-9]]\\s*//; s/^\"\|\"$//g"' } \ |
| '' \ |
| 'server options' C-s { run 'tmux -Ldm -f/dev/null start \; show -s' } \ |
| 'window options' w { run 'tmux -Ldm -f/dev/null start \; show -gw' } \ |
| 'session options' s { run 'tmux -Ldm -f/dev/null start \; show -g' } \ |
| } |
| |
| # By default, `detach-session` is bound to `d`. |
| # I find that too easy to press, so we move it to `@`. |
| # Why `@`? |
| # I didn't find anything better, and it seems hard to press by accident... |
| bind @ detach |
| |
| # resize pane |
| bind -r C-h resizep -L 5 |
| bind -r C-j resizep -D 5 |
| bind -r C-k resizep -U 5 |
| bind -r C-l resizep -R 5 |
| |
| # focus neighboring panes |
| # Do *not* make them repeatable. It leads to a confusing user experience.{{{ |
| # |
| # Example: Press `pfx k` to focus the pane above which is running Vim, then press `j`. |
| # Expected: Vim scrolls downward. |
| # Actual Result: tmux focuses back the pane below. |
| # |
| # If you have many panes, and you need to focus one, try `display-panes` |
| # (currently bound to `pfx *`), then press the index of the desired pane. |
| #}}} |
| bind h selectp -L |
| bind l selectp -R |
| bind j selectp -D |
| bind k selectp -U |
| |
| # move pane to the far right/left/bottom/top |
| bind H split-window -fhb \; swap-pane -t ! \; kill-pane -t ! |
| bind L split-window -fh \; swap-pane -t ! \; kill-pane -t ! |
| bind J split-window -fv \; swap-pane -t ! \; kill-pane -t ! |
| bind K split-window -fvb \; swap-pane -t ! \; kill-pane -t ! |
| |
| # Toggle mouse. |
| # Temporarily preventing tmux from handling the mouse can be useful in some |
| # terminals to copy text in the clipboard. |
| # Why `M` for the lhs?{{{ |
| # |
| # It provides a good mnemonic for “mouse”. |
| # I don't use `C-m` nor `M-m` because, atm, they're used to display some default menus. |
| # |
| # However, note that `pfx M` is used by default to clear the marked pane (`select-pane -M`). |
| # It's not a big deal to lose it, because we can get the same result by focusing |
| # the marked pane and then pressing `pfx m` (`select-pane -m`). |
| # The latter marks the pane if it's not already, or clears the mark otherwise. |
| #}}} |
| bind M set -g mouse \; display 'mouse: #{?#{mouse},ON,OFF}' |
| |
| # toggle 'monitor-activity' in current window |
| bind C-a set -w monitor-activity \; display 'monitor-activity: #{?#{monitor-activity},ON,OFF}' |
| |
| # kill all panes except the current one (similar to `:only` or `C-w o` in Vim) |
| bind o kill-pane -a |
| |
| # paste last tmux buffer |
| # Do *not* choose a key too easy to type.{{{ |
| # |
| # It's dangerous. |
| # In Vim, the contents of the buffer will be typed, which will have unexpected |
| # results, unless you're in insert mode. |
| #}}} |
| bind C-p paste-buffer -p |
| |
| # choose and paste arbitrary tmux buffer |
| # What's `-Z`?{{{ |
| # |
| # It makes tmux zoom the pane so that it takes the whole window. |
| #}}} |
| # `-F`?{{{ |
| # |
| # It specifies the format with which each buffer should be displayed. |
| # In it, you can use these replacement variables: |
| # |
| # ┌────────────────┬─────────────────────────────┐ |
| # │ buffer_created │ creation date of the buffer │ |
| # ├────────────────┼─────────────────────────────┤ |
| # │ buffer_name │ name of the buffer │ |
| # ├────────────────┼─────────────────────────────┤ |
| # │ buffer_sample │ starting text of the buffer │ |
| # ├────────────────┼─────────────────────────────┤ |
| # │ buffer_size │ size of the buffer │ |
| # └────────────────┴─────────────────────────────┘ |
| # |
| # Note that even with an empty format, tmux will still display the name of a |
| # buffer followed by a colon. |
| # So, `buffer_name` is not very useful (unless you want to print the name of a |
| # buffer twice). |
| #}}} |
| # the `-p` argument passed to `paste-buffer`?{{{ |
| # |
| # It prevents the shell from automatically running a pasted text which contains |
| # a newline. |
| # |
| # See `man tmux /^\s*paste-buffer` |
| #}}} |
| bind p choose-buffer -Z -F '#{buffer_sample}' "paste-buffer -p -b '%%'" |
| |
| # similar to `C-w r` and `C-w R` in Vim |
| bind -r r rotate-window -D \; selectp -t :.+ |
| bind -r R rotate-window -U \; selectp -t :.- |
| |
| # reload tmux config |
| bind C-r source "$HOME/.config/tmux/tmux.conf" \; display 'Configuration reloaded' |
| |
| # You need to install the `urlscan(1)` utility for this key binding to work. |
| # Why do you include `deleteb` inside the shell cmd run by `split-window`?{{{ |
| # |
| # If you move it outside: |
| # |
| # bind u capture-pane \; split-window -l 10 'urlscan =(tmux showb)' \; deleteb |
| # |
| # `urlscan(1)` can't find any link. |
| # |
| # This is because: |
| # |
| # 1. deleteb is run before `$ tmux showb` |
| # 2. thus `$ tmux showb` outputs nothing |
| # 3. urlscan finds no url |
| # |
| # You can get the same effect by running: |
| # |
| # $ tmux split-window 'urlscan =(echo "")' |
| #}}} |
| # We name the tmux buffer so that we can remove it reliably at the end.{{{ |
| # |
| # Indeed, you might copy some text while the urlscan pane is opened (not |
| # necessarily in the latter; in any session, window, pane), creating a new |
| # buffer at the top of the stack. |
| # If you just run `$ tmux deleteb`, it would remove that buffer instead of the |
| # buffer created by `capture-pane`. |
| #}}} |
| # Why `head -c -1`?{{{ |
| # |
| # If there is no url in the tmux buffer, we want tmux to automatically close the |
| # pane. That's why we use `ifne(1)` later; it runs the second `urlscan(1)` on |
| # the condition that the output of the previous one is empty. The problem is |
| # that even when the first `urlscan(1)` fails to find any url, it still outputs |
| # a single newline. We need to remove it, so that `ifne(1)` works as expected. |
| #}}} |
| bind u capture-pane -b urlscan \; \ |
| split-window -l 10 " |
| tmux showb -b urlscan | \ |
| urlscan --no-browser | \ |
| head -c -1 | \ |
| ifne urlscan --compact \ |
| --dedupe \ |
| --nohelp \ |
| --regex \"(http|ftp)s?://[^ '\\\">)}\\\\]]+\" \ |
| ; tmux deleteb -b urlscan |
| " |
| |
| # focus next pane |
| bind -r C-w selectp -t :.+ |
| |
| # similar to `C-w x` in Vim |
| bind x swap-pane -U |
| bind X swap-pane -D |
| # }}}1 |
| # Hooks {{{1 |
| # Don't use this hook: `set-hook -g after-split-window 'selectl even-vertical'`{{{ |
| # |
| # You wouldn't be able to split vertically anymore. |
| # Splitting vertically would result in an horizontal split no matter what. |
| # |
| # The hook is given as an example in `man tmux`; its purpose is to resize |
| # equally all the panes whenever you split a pane horizontally. |
| #}}} |
| |
| set-hook -g pane-focus-out '' |
| |
| # Plugins {{{1 |
| |
| # Why the guard?{{{ |
| # |
| # To prevent the plugins from re-installing their key bindings every time we |
| # resource `tmux.conf`. |
| # Indeed, we only unbind the key bindings from the copy-mode table once. |
| # |
| # Besides, it's probably a bad idea to resource plugins. |
| #}}} |
| if '[ "$TERM" != "#{default-terminal}" ]' { source "$HOME/.config/tmux/plugins/run" } |
| |
| # Rebind {{{1 |
| |
| # Purpose:{{{ |
| # |
| # The tmux-yank plugin installs this key binding: |
| # |
| # bind-key -T copy-mode-vi Y send-keys -X copy-pipe-and-cancel "tmux paste-buffer" |
| # |
| # It copies the selection, quit copy mode, then paste the buffer. |
| # |
| # However, it doesn't support the bracketed paste mode. |
| # So we redefine the key binding, and pass `-p` to `paste-buffer` to surround |
| # the text with the sequences `Esc [ 200 ~` and `Esc [ 201 ~`. |
| # This way, if we select a text containing a newline, then press `p`, it's not |
| # automatically run by the shell. |
| # |
| # From `man tmux /^\s*paste-buffer`: |
| # |
| # > If -p is specified, paste bracket control codes are inserted around the |
| # > buffer if the application has requested bracketed paste mode. |
| # |
| # Note that this requires that the shell supports the bracketed paste mode. |
| # I.e. if you're using zsh, you need zsh version 5.1 or greater, and if you're |
| # using bash, you need bash 4.4 or greater. |
| # |
| # --- |
| # |
| # The `-p` option was added to tmux in the commit `f4fdddc`. |
| # According to the changelog, this was somewhere between tmux 1.6 and 1.7. |
| # |
| # --- |
| # |
| # Note that the original key binding used `copy-pipe-and-cancel` which – while |
| # working – doesn't make sense; you can't pipe anything to `$ tmux paste-buffer`, |
| # since it doesn't read its input. |
| #}}} |
| bind -T copy-mode-vi p send -X copy-selection-and-cancel \; paste-buffer -p \; deleteb |
| # ^^ |
| |
| # Unbind {{{1 |
| |
| # How to find the default key bindings installed with no config?{{{ |
| # |
| # $ tmux -Lx -f/dev/null new |
| # C-b ? |
| # VG$ |
| # Enter |
| # $ vim |
| # i |
| # C-b ] |
| # |
| # Make sure to release `Ctrl` before pressing `]`. |
| #}}} |
| # How to unbind `#`, `~`, `'`, `"`?{{{ |
| # |
| # Quote the key (with single or double quotes). |
| # |
| # From `man tmux /^KEY BINDINGS`: |
| # |
| # > Note that to bind the ‘"’ or ‘'’ keys, quotation marks are necessary. |
| #}}} |
| # How to unbind `;`?{{{ |
| # |
| # Escape it. |
| # |
| # From `man tmux /^COMMANDS`: |
| # |
| # > A literal semicolon may be included by escaping it with a backslash (for |
| # > example, when specifying a command sequence to bind-key). |
| #}}} |
| |
| # TODO: |
| # Remove all default key bindings which you're not interested in. |
| # Some of them could be hit by accident. |
| # Keep only the ones you really use. |
| # Besides, it will give us a smaller table of key bindings, which will be easier |
| # to read when we have an issue with one of our key bindings. |
| # Have a look at `~/Desktop/tmux.md`. |
| |
| # prefix {{{2 |
| |
| # send-prefix |
| unbind C-b |
| # rotate-window |
| unbind C-o |
| # show-messages |
| unbind '~' |
| # split-window |
| unbind '"' |
| # choose-buffer -Z |
| unbind = |
| # detach-client |
| unbind d |
| # next-window |
| unbind n |
| # swap-pane -[UD] |
| unbind '{' |
| unbind '}' |
| # select-pane -[UDLR] |
| unbind Up |
| unbind Down |
| unbind Left |
| unbind Right |
| # tmux clear-history (tmux-logging) |
| unbind M-c |
| # rotate-window -D |
| unbind M-o |
| # resize-pane -U 5 |
| unbind M-up |
| # resize-pane -D 5 |
| unbind M-down |
| # resize-pane -L 5 |
| unbind M-left |
| # resize-pane -R 5 |
| unbind M-right |
| # resize-pane -[UDLR] |
| unbind C-Up |
| unbind C-Down |
| unbind C-Left |
| unbind C-Right |
| # run ~/.config/tmux/plugins/tmux-logging/scripts/screen_capture.sh (tmux-logging) |
| unbind M-p |
| # run ~/.config/tmux/plugins/tmux-logging/scripts/save_complete_history.sh (tmux-logging) |
| unbind M-P |
| # resize-pane -Z |
| unbind z |
| # command-prompt -i -p / { send-keys -X search-forward-incremental "%%" } |
| unbind / |
| |
| # copy-mode-vi {{{2 |
| |
| # By default, it's bound to `send-keys -X copy-pipe-and-cancel`.{{{ |
| # |
| # I don't like that, because I often select some text with the mouse by accident |
| # (or when I'm bored); when that happens, obviously, tmux creates a buffer. |
| # |
| # This pollutes our list of buffers, and makes the interesting ones harder to |
| # find. Besides, if when I want to copy some text, I will certainly not do it |
| # with the mouse (not accurate enough). |
| #}}} |
| unbind -T copy-mode-vi MouseDragEnd1Pane |
| # send-keys -X begin-selection |
| unbind -T copy-mode-vi Space |
| # send-keys -X copy-pipe-and-cancel |
| unbind -T copy-mode-vi C-j |
| # send -X copy-pipe-and-cancel 'xsel -i --clipboard; tmux paste-buffer' (tmux-yank) |
| unbind -T copy-mode-vi M-y |
| # (tmux-yank) |
| unbind -T copy-mode-vi Y |
| |
| # copy-mode {{{2 |
| |
| # We don't need the key bindings from the copy-mode table; we use the copy-mode-*vi* table. |
| # Why the guard?{{{ |
| # |
| # Once the table is empty, it's removed. |
| # So, if you later try to unbind a key binding from it, an error will be raised: |
| # |
| # Table copy-mode doesn't exist |
| # |
| # Which can be repeated for every key binding you try to remove: |
| # |
| # Table copy-mode doesn't exist |
| # Table copy-mode doesn't exist |
| # ... |
| # |
| # Run `show-messages` to see them. |
| # |
| # This is annoying when you reload `tmux.conf`. |
| #}}} |
| if '[ "$TERM" != "#{default-terminal}" ]' { source "$HOME/.config/tmux/unbind-copy-mode.conf" } |
| # }}}1 |