├── LICENSE ├── README.md └── matryoshka.tmux /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2024 Nicola De Angeli 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # tmux-matryoshka 2 | 3 | Plugin for nested tmux workflows. 4 | 5 | ## Use case 6 | 7 | ### Problem 8 | 9 | You use tmux for workflows on both your local and non-local machines and you would like to use the local tmux to connect to remote machines. Unfortunately, your local tmux captures all tmux keybinds, making you unable to communicate with the remote tmux instance. 10 | 11 | ### Solution 12 | 13 | Use tmux-matryoshka to temporarily disable the outer, local tmux and forget about it. All tmux keybinds are now forwarded to the remote, nested tmux instance. You can repeat this process if you need to communicate with other furtherly nested tmux instance, be it local or remote.[^1] 14 | 15 | When needed, you can easily re-enable the disabled tmux instances in the reverse order they were disabled. 16 | 17 | ## Installation 18 | 19 | ### Installation with Tmux Plugin Manager 20 | 21 | Add this repository as a [TPM](https://github.com/tmux-plugins/tpm) plugin in your `.tmux.conf` file: 22 | 23 | ```conf 24 | set -g @plugin 'niqodea/tmux-matryoshka' 25 | ``` 26 | 27 | Press `prefix + I` in Tmux environment to install it. 28 | 29 | ### Manual Installation 30 | 31 | Clone this repository: 32 | 33 | ```bash 34 | git clone https://github.com/niqodea/tmux-matryoshka.git ~/.tmux/plugins/tmux-matryoshka 35 | ``` 36 | 37 | Add this line to your `.tmux.conf` file: 38 | 39 | ```conf 40 | run-shell ~/.tmux/plugins/tmux-matryoshka/matryoshka.tmux 41 | ``` 42 | 43 | Reload tmux configuration file with: 44 | 45 | ```sh 46 | tmux source-file ~/.tmux.conf 47 | ``` 48 | 49 | ## Usage 50 | 51 | In order to work, the keybinds for this plugin are set without tmux prefix. With the default keybinds:[^2] 52 | 53 | - press `F1` to disable the outer-most active tmux instance 54 | - press `F2` to enable the inner-most inactive tmux instance 55 | - press `F3` to recursively enable all tmux instances[^3] 56 | 57 | ## Configuration 58 | 59 | The following configuration options are available: 60 | 61 | ```conf 62 | # keybind to disable outer-most active tmux 63 | set -g @matryoshka_down_keybind 'M-d' 64 | # keybind to enable inner-most inactive tmux 65 | set -g @matryoshka_up_keybind 'M-u' 66 | # keybind to recursively enable all tmux instances 67 | set -g @matryoshka_up_recursive_keybind 'M-U' 68 | 69 | # to set the inactive status style, you can choose either to provide a fixed value... 70 | set -g @matryoshka_inactive_status_style_strategy 'assignment' 71 | set -g @matryoshka_inactive_status_style 'bg=colour238,fg=colour245' 72 | # ...or patch the existing status style with sed substitutions 73 | set -g @matryoshka_inactive_status_style_strategy 'sed' 74 | set -g @matryoshka_inactive_status_style 's/green/colour238/g; s/black/colour245/g' 75 | 76 | # name of the option for the style of the status line 77 | # set if you rely on something other than the default 'status-style' option for it 78 | set -g @matryoshka_status_style_option 'my-status-style' 79 | ``` 80 | 81 | Include them in your `.tmux.conf` before running the setup. 82 | 83 | ## Implementation 84 | 85 | This plugin manages nested tmux sessions using a counter environment variable for each tmux instance in the nested hierarchy. When a tmux layer is disabled, the counter increments, keeping track of how deep you are in the nested sessions. Re-enabling tmux instances decrements this counter, ensuring they are reactivated in reverse order, which feels intuitive and natural. 86 | 87 | ## Comparisons with Alternatives 88 | 89 | tmux-matryoshka is not the first plugin to streamline nested tmux workflows. 90 | Here is how it differentiates itself from other known plugins of its kind. 91 | 92 | * [tmux-suspend](https://github.com/MunifTanjim/tmux-suspend): tmux-matryoshka supports arbitrary levels of nesting, not just one 93 | * [nested-tmux](https://github.com/aleclearmind/nested-tmux): tmux-matryoshka works seamlessly for nested tmux sessions also over ssh, not just locally 94 | 95 | ## License 96 | 97 | Licensed under the MIT License. Check the [LICENSE](./LICENSE) file for details. 98 | 99 | ## Acknowledgements 100 | 101 | This plugin was inspired by samohskin's popular [`toggle_keybindings.tmux.conf`](https://gist.github.com/samoshkin/05e65f7f1c9b55d3fc7690b59d678734) Github gist. 102 | 103 | 104 | [^1]: You could need this if, for example, you need to access a tmux server running inside a docker container on a remote machine 105 | [^2]: Customizing your keybinds is suggested 106 | [^3]: You can also use this to fix broken states, e.g. an inactive tmux instance nested inside an active tmux instance 107 | -------------------------------------------------------------------------------- /matryoshka.tmux: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env sh 2 | 3 | set -eu 4 | 5 | # >>> Configuration options and default values 6 | down_keybind="$(tmux show-option -gqv @matryoshka_down_keybind)" 7 | if [ -z "$down_keybind" ]; then down_keybind='F1'; fi 8 | 9 | up_keybind="$(tmux show-option -gqv @matryoshka_up_keybind)" 10 | if [ -z "$up_keybind" ]; then up_keybind='F2'; fi 11 | 12 | up_recursive_keybind="$(tmux show-option -gqv @matryoshka_up_recursive_keybind)" 13 | if [ -z "$up_recursive_keybind" ]; then up_recursive_keybind='F3'; fi 14 | 15 | inactive_status_style_strategy="$(tmux show-option -gqv @matryoshka_inactive_status_style_strategy)" 16 | if [ -z "$inactive_status_style_strategy" ]; then inactive_status_style_strategy='assignment'; fi 17 | 18 | inactive_status_style="$(tmux show-option -gqv @matryoshka_inactive_status_style)" 19 | if [ -z "$inactive_status_style" ]; then inactive_status_style='bg=colour238,fg=colour245'; fi 20 | 21 | status_style_option="$(tmux show-option -gqv @matryoshka_status_style_option)" 22 | if [ -z "$status_style_option" ]; then status_style_option='status-style'; fi 23 | # <<< Configuration options and default values 24 | 25 | MATRYOSHKA_COUNTER_ENV_NAME='MATRYOSHKA_COUNTER' 26 | MATRYOSHKA_INACTIVE_TABLE_NAME='matryoshka-inactive' 27 | 28 | # Down: disable the outer-most active tmux 29 | if [ "$inactive_status_style_strategy" = 'assignment' ]; then 30 | status_style_update="set-option \"$status_style_option\" \"$inactive_status_style\"" 31 | elif [ "$inactive_status_style_strategy" = 'sed' ]; then 32 | status_style_update="run-shell 'tmux set-option \"$status_style_option\" \"\$(tmux show-option -gqv \"$status_style_option\" | sed \"$inactive_status_style\")\"'" 33 | else 34 | >&2 echo "Error: unknown inactive status style strategy '$inactive_status_style_strategy'" 35 | exit 1 36 | fi 37 | tmux bind -n "$down_keybind" \ 38 | "set-environment \"$MATRYOSHKA_COUNTER_ENV_NAME\" 1 ; "\ 39 | "set key-table \"$MATRYOSHKA_INACTIVE_TABLE_NAME\" ; "\ 40 | 'set prefix None ; '\ 41 | 'set prefix2 None ; '\ 42 | "$status_style_update" 43 | tmux bind -T "$MATRYOSHKA_INACTIVE_TABLE_NAME" "$down_keybind" \ 44 | "run-shell 'tmux set-environment \"$MATRYOSHKA_COUNTER_ENV_NAME\" \$(( \$(tmux show-environment \"$MATRYOSHKA_COUNTER_ENV_NAME\" | cut -d = -f 2) + 1 ))' ; "\ 45 | "send-keys \"$down_keybind\"" 46 | 47 | # Up: enable the inner-most inactive tmux 48 | tmux bind -n "$up_keybind" \ 49 | 'display-message "Error: no inactive tmux to enable"' 50 | tmux bind -T "$MATRYOSHKA_INACTIVE_TABLE_NAME" "$up_keybind" \ 51 | "run-shell 'tmux set-environment \"$MATRYOSHKA_COUNTER_ENV_NAME\" \$(( \$(tmux show-environment \"$MATRYOSHKA_COUNTER_ENV_NAME\" | cut -d = -f 2) - 1 ))' ; "\ 52 | "if-shell '[ \"\$(tmux show-environment \"$MATRYOSHKA_COUNTER_ENV_NAME\" | cut -d = -f 2)\" -eq 0 ]' "\ 53 | "'set-environment -u \"$MATRYOSHKA_COUNTER_ENV_NAME\" ; set -u prefix ; set -u prefix2 ; set -u key-table ; set -u \"$status_style_option\"' "\ 54 | "'send-keys \"$up_keybind\"'" 55 | 56 | # Up recursive: enable all tmux instances recursively 57 | # Useful to go back to top-level tmux or reset broken counters 58 | tmux bind -n "$up_recursive_keybind" \ 59 | 'send-keys ""' # No op 60 | tmux bind -T "$MATRYOSHKA_INACTIVE_TABLE_NAME" "$up_recursive_keybind" \ 61 | "send-keys \"$up_recursive_keybind\" ; "\ 62 | "set-environment -u \"$MATRYOSHKA_COUNTER_ENV_NAME\" ; "\ 63 | 'set -u key-table ; '\ 64 | 'set -u prefix ; '\ 65 | 'set -u prefix2 ; '\ 66 | "set -u \"$status_style_option\"" 67 | 68 | # Note: `$(tmux show-environment MATRYOSHKA_COUNTER | cut -d = -f 2)` is the simplest way to retrieve the 69 | # value of MATRYOSHKA_COUNTER, for some reason 70 | --------------------------------------------------------------------------------