├── LICENSE ├── .gitignore ├── scripts └── gendocs.sh ├── README.md └── tmux-autoreload.tmux /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2021 Maddison Hellstrom 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 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | 2 | # Created by https://www.toptal.com/developers/gitignore/api/linux,macos,windows,vim,git,zsh 3 | # Edit at https://www.toptal.com/developers/gitignore?templates=linux,macos,windows,vim,git,zsh 4 | 5 | ### Git ### 6 | # Created by git for backups. To disable backups in Git: 7 | # $ git config --global mergetool.keepBackup false 8 | *.orig 9 | 10 | # Created by git when using merge tools for conflicts 11 | *.BACKUP.* 12 | *.BASE.* 13 | *.LOCAL.* 14 | *.REMOTE.* 15 | *_BACKUP_*.txt 16 | *_BASE_*.txt 17 | *_LOCAL_*.txt 18 | *_REMOTE_*.txt 19 | 20 | ### Linux ### 21 | *~ 22 | 23 | # temporary files which can be created if a process still has a handle open of a deleted file 24 | .fuse_hidden* 25 | 26 | # KDE directory preferences 27 | .directory 28 | 29 | # Linux trash folder which might appear on any partition or disk 30 | .Trash-* 31 | 32 | # .nfs files are created when an open file is removed but is still being accessed 33 | .nfs* 34 | 35 | ### macOS ### 36 | # General 37 | .DS_Store 38 | .AppleDouble 39 | .LSOverride 40 | 41 | # Icon must end with two 42 | Icon 43 | 44 | # Thumbnails 45 | ._* 46 | 47 | # Files that might appear in the root of a volume 48 | .DocumentRevisions-V100 49 | .fseventsd 50 | .Spotlight-V100 51 | .TemporaryItems 52 | .Trashes 53 | .VolumeIcon.icns 54 | .com.apple.timemachine.donotpresent 55 | 56 | # Directories potentially created on remote AFP share 57 | .AppleDB 58 | .AppleDesktop 59 | Network Trash Folder 60 | Temporary Items 61 | .apdisk 62 | 63 | ### Vim ### 64 | # Swap 65 | [._]*.s[a-v][a-z] 66 | !*.svg # comment out if you don't need vector files 67 | [._]*.sw[a-p] 68 | [._]s[a-rt-v][a-z] 69 | [._]ss[a-gi-z] 70 | [._]sw[a-p] 71 | 72 | # Session 73 | Session.vim 74 | Sessionx.vim 75 | 76 | # Temporary 77 | .netrwhist 78 | # Auto-generated tag files 79 | tags 80 | # Persistent undo 81 | [._]*.un~ 82 | 83 | ### Windows ### 84 | # Windows thumbnail cache files 85 | Thumbs.db 86 | Thumbs.db:encryptable 87 | ehthumbs.db 88 | ehthumbs_vista.db 89 | 90 | # Dump file 91 | *.stackdump 92 | 93 | # Folder config file 94 | [Dd]esktop.ini 95 | 96 | # Recycle Bin used on file shares 97 | $RECYCLE.BIN/ 98 | 99 | # Windows Installer files 100 | *.cab 101 | *.msi 102 | *.msix 103 | *.msm 104 | *.msp 105 | 106 | # Windows shortcuts 107 | *.lnk 108 | 109 | ### Zsh ### 110 | # Zsh compiled script + zrecompile backup 111 | *.zwc 112 | *.zwc.old 113 | 114 | # Zsh completion-optimization dumpfile 115 | *zcompdump* 116 | 117 | # Zsh zcalc history 118 | .zcalc_history 119 | 120 | # A popular plugin manager's files 121 | ._zinit 122 | .zinit_lstupd 123 | 124 | # zdharma/zshelldoc tool's files 125 | zsdoc/data 126 | 127 | # robbyrussell/oh-my-zsh/plugins/per-directory-history plugin's files 128 | # (when set-up to store the history in the local directory) 129 | .directory_history 130 | 131 | # MichaelAquilina/zsh-autoswitch-virtualenv plugin's files 132 | # (for Zsh plugins using Python) 133 | .venv 134 | 135 | # Zunit tests' output 136 | /tests/_output/* 137 | !/tests/_output/.gitkeep 138 | 139 | # End of https://www.toptal.com/developers/gitignore/api/linux,macos,windows,vim,git,zsh 140 | -------------------------------------------------------------------------------- /scripts/gendocs.sh: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | 3 | # Copyright (C) 2020-2021 Maddison Hellstrom , MIT License. 4 | 5 | set -Eeuo pipefail 6 | if [[ ${BASH_VERSINFO[0]} -ge 5 || (${BASH_VERSINFO[0]} -eq 4 && ${BASH_VERSINFO[1]} -ge 4) ]]; then 7 | shopt -s inherit_errexit 8 | fi 9 | 10 | declare -g self prog basedir reporoot 11 | self="$(readlink -e "${BASH_SOURCE[0]}")" 12 | prog="$(basename "$self")" 13 | basedir="$(realpath -m "$self/..")" 14 | reporoot="$(realpath -m "$basedir/..")" 15 | 16 | # gendocs configuration {{{ 17 | 18 | declare -g main="${reporoot}/tmux-autoreload.tmux" 19 | 20 | declare -gA targets=( 21 | [readme]="$reporoot/README.md" 22 | ) 23 | 24 | declare -gi copyright_start=2021 25 | 26 | function target_readme() { 27 | section -s USAGE -c <<< "$("$main" -h 2>&1)" 28 | section -s LICENSE << EOF 29 | © ${copyright_start}$( (($(date +%Y) == copyright_start)) || date +-%Y) Maddison Hellstrom 30 | 31 | Released under the MIT License. 32 | EOF 33 | } 34 | 35 | # }}} 36 | 37 | declare -gA sections 38 | 39 | function section() { 40 | local section 41 | local -i code=0 42 | local lang 43 | 44 | local opt OPTARG 45 | local -i OPTIND 46 | while getopts "s:cC:" opt "$@"; do 47 | case "$opt" in 48 | s) 49 | section="$OPTARG" 50 | ;; 51 | c) 52 | code=1 53 | ;; 54 | C) 55 | code=1 56 | lang="$OPTARG" 57 | ;; 58 | \?) 59 | return 1 60 | ;; 61 | esac 62 | done 63 | shift $((OPTIND - 1)) 64 | 65 | local -a lines=('') 66 | 67 | if [[ $code -eq 1 ]]; then 68 | lines+=('```'"${lang:-}" '') 69 | fi 70 | 71 | mapfile -tO ${#lines[@]} lines 72 | 73 | if [[ $code -eq 1 ]]; then 74 | lines+=('' '```') 75 | fi 76 | 77 | sections["$section"]="$(printf '%s\n' "${lines[@]}")\n" 78 | } 79 | 80 | function regen_section() { 81 | local section="$1" 82 | local content="${sections[$section]}" 83 | awk < "$target" -v "section=$section" -v "content=$content" ' 84 | BEGIN { 85 | d = 0 86 | } 87 | 88 | { 89 | if (match($0, "^$")) { 90 | d = 1 91 | print $0 92 | print content 93 | next 94 | } 95 | if (match($0, "^$")) { 96 | d = 0 97 | print $0 98 | next 99 | } 100 | } 101 | 102 | d == 0 { 103 | print $0 104 | } 105 | ' 106 | } 107 | 108 | function main() { 109 | local opt OPTARG 110 | local -i OPTIND 111 | while getopts "h" opt "$@"; do 112 | case "$opt" in 113 | h) 114 | echo "usage: $prog [opt].. [target].." >&2 115 | return 0 116 | ;; 117 | \?) 118 | return 1 119 | ;; 120 | esac 121 | done 122 | shift $((OPTIND - 1)) 123 | 124 | local -a targets_selected=("${!targets[@]}") 125 | 126 | if [[ $# -gt 0 ]]; then 127 | targets_selected=("$@") 128 | fi 129 | 130 | local t target 131 | for t in "${targets_selected[@]}"; do 132 | [[ -v "targets[$t]" ]] || { 133 | echo "unknown target: $t" >&2 134 | return 1 135 | } 136 | target="${targets["$t"]}" 137 | [[ -e "$target" ]] || { 138 | echo "target file not found: $target" >&2 139 | return 1 140 | } 141 | sections=() 142 | "target_${t}" || { 143 | echo "unknown target: $t" 144 | return 1 145 | } 146 | local s 147 | for s in "${!sections[@]}"; do 148 | regen_section "$s" > "${target}_" 149 | mv "${target}_" "$target" 150 | done 151 | done 152 | } 153 | 154 | main "$@" 155 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # tmux-autoreload [![version](https://img.shields.io/github/v/tag/b0o/tmux-autoreload?style=flat&color=yellow&label=version&sort=semver)](https://github.com/b0o/tmux-autoreload/releases) [![license: MIT](https://img.shields.io/github/license/b0o/tmux-autoreload?style=flat&color=green)](https://mit-license.org) 2 | 3 | tmux-autoreload watches your tmux configuration file and automatically reloads 4 | it on change. 5 | 6 | ## Install 7 | 8 | #### Dependencies 9 | 10 | - [entr](https://github.com/eradman/entr) 11 | 12 | ## Installation 13 | 14 | To install tmux-autoreload with TPM (https://github.com/tmux-plugins/tpm), add the 15 | following line to the end of your tmux configuration file: 16 | 17 | ```sh 18 | set-option -g @plugin 'b0o/tmux-autoreload' 19 | ``` 20 | 21 | Then, inside tmux, press `prefix + I` to fetch the plugin. 22 | 23 | If you don't use a plugin manager, git clone tmux-autoreload to the location of your 24 | choice and run it directly: 25 | 26 | ```sh 27 | run-shell "/path/to/tmux-autoreload/tmux-autoreload.tmux" 28 | ``` 29 | 30 | ## Setup 31 | 32 | Once installed, you should be good to go unless you use non-standard 33 | configuration file paths or want to customize how tmux-autoreload behaves. 34 | 35 | ### Configuration file paths 36 | 37 | If your tmux config file is at a non-standard location or if you have multiple, 38 | specify them in `@tmux-autoreload-configs`, separated by commas: 39 | 40 | ```sh 41 | set-option -g @tmux-autoreload-configs '/path/to/configs/a.conf,/path/to/configs/b.conf' 42 | ``` 43 | 44 | ### Entrypoints 45 | 46 | Normally, tmux-autoreload will source whichever file changed. If you wish to 47 | source a specific set of files when any configuration file changes, use 48 | `@tmux-autoreload-entrypoints`: 49 | 50 | ```sh 51 | set-option -g @tmux-autoreload-entrypoints '/path/to/entrypoint.conf' 52 | ``` 53 | 54 | You can specify multiple entrypoints separated by commas. All entrypoints 55 | will be sourced when any watched file changes. 56 | 57 | Set `@tmux-autoreload-entrypoints` to 1 to use the standard tmux configuration 58 | files as entrypoints, usually `/etc/tmux.conf` and `~/.tmux.conf.` You can see 59 | these files with: 60 | 61 | ```sh 62 | tmux display-message -p "#{config_files}" 63 | ``` 64 | 65 | #### Entrypoint Notes 66 | 67 | - If entrypoints are configured, a changed file itself will not necessarily 68 | be reloaded unless it's an entrypoint or is sourced by an entrypoint. 69 | 70 | - Entrypoints will not be watched unless they're a standard tmux 71 | configuration file like `~/.tmux.conf` or are included in `@tmux-autoreload-configs.` 72 | 73 | ### All Options 74 | 75 | ``` 76 | @tmux-autoreload-configs (Default: unset) 77 | A comma-delimited list of paths to configuration files which should be 78 | watched in addition to the base tmux configuration files. 79 | 80 | @tmux-autoreload-entrypoints (Default: unset) 81 | A comma-delimited list of paths to configuration files which should be 82 | reloaded when any watched configuration file changes. If unset, the changed 83 | file itself will be reloaded. 84 | 85 | If set, only the entrypoints will be reloaded, not necessarily the changed 86 | file. 87 | 88 | If set to 1, the base tmux configuration files are used as the entrypoints 89 | (you can see the base configuration files with the command tmux 90 | display-message -p "#{config_files}"). 91 | 92 | @tmux-autoreload-quiet 0|1 (Default: 0) 93 | If set to 1, tmux-autoreload will not display status messages. 94 | ``` 95 | 96 | ## Advanced Usage 97 | 98 | 99 | 100 | ``` 101 | 102 | Usage: tmux-autoreload.tmux [-f] [OPT...] 103 | Automatically reloads your tmux configuration files on change. 104 | 105 | Options 106 | -h Display usage information. 107 | -v Display tmux-autoreload version and copyright information. 108 | -f Run in foreground (do not fork). 109 | -k Kill the running tmux-autoreload instance. 110 | -s Show tmux-autoreload status 111 | 112 | Installation 113 | To install tmux-autoreload with TPM (https://github.com/tmux-plugins/tpm), add the 114 | following line to the end of your tmux configuration file: 115 | set-option -g @plugin 'b0o/tmux-autoreload' 116 | 117 | If you don't use a plugin manager, git clone tmux-autoreload to the location of your 118 | choice and run it directly: 119 | run-shell "/path/to/tmux-autoreload/tmux-autoreload.tmux" 120 | 121 | Once installed, you should be good to go unless you use non-standard 122 | configuration file paths or want to customize how tmux-autoreload behaves. 123 | 124 | Configuration file paths 125 | If your config file is at a non-standard location or if you have multiple, 126 | specify them in @tmux-autoreload-configs, separated by commas: 127 | set-option -g @tmux-autoreload-configs '/path/to/configs/a.conf,/path/to/configs/b.conf' 128 | 129 | Entrypoints 130 | Normally, tmux-autoreload will source whichever file changed. If you wish to 131 | source a specific set of files when any configuration file changes, use 132 | @tmux-autoreload-entrypoints: 133 | set-option -g @tmux-autoreload-entrypoints '/path/to/entrypoint.conf' 134 | 135 | You can specify multiple entrypoints separated by commas. All entrypoints 136 | will be sourced when any watched file changes. 137 | 138 | Set @tmux-autoreload-entrypoints to 1 to use the standard tmux configuration 139 | files as entrypoints, usually /etc/tmux.conf and ~/.tmux.conf. You can see 140 | these files with: 141 | tmux display-message -p "#{config_files}". 142 | 143 | Entrypoint Notes: 144 | - If entrypoints are configured, a changed file itself will not necessarily 145 | be reloaded unless it's an entrypoint or is sourced by an entrypoint. 146 | 147 | - Entrypoints will not be watched unless they're a standard tmux 148 | configuration file like ~/.tmux.conf or are included in @tmux-autoreload-configs. 149 | 150 | Other Options 151 | @tmux-autoreload-quiet 0|1 (Default: 0) 152 | If set to 1, tmux-autoreload will not display messages 153 | 154 | ``` 155 | 156 | 157 | 158 | ## License 159 | 160 | 161 | 162 | © 2021 Maddison Hellstrom 163 | 164 | Released under the MIT License. 165 | 166 | 167 | -------------------------------------------------------------------------------- /tmux-autoreload.tmux: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env bash 2 | 3 | # Automatically reloads your tmux configuration files on change. 4 | # 5 | # Copyright 2021 Maddison Hellstrom , MIT License. 6 | 7 | set -Eeuo pipefail 8 | if [[ ${BASH_VERSINFO[0]} -ge 5 || (${BASH_VERSINFO[0]} -eq 4 && ${BASH_VERSINFO[1]} -ge 4) ]]; then 9 | shopt -s inherit_errexit 10 | fi 11 | 12 | function years_from() { 13 | local from="$1" to 14 | to="${2:-$(date +%Y)}" 15 | if [[ "$from" == "$to" ]]; then 16 | echo "$from" 17 | else 18 | echo "$from-$to" 19 | fi 20 | } 21 | 22 | declare -g self prog name 23 | self="$(realpath -e "${BASH_SOURCE[0]}")" 24 | prog="$(basename "$self")" 25 | name="${prog%.tmux}" 26 | 27 | declare -gr version="0.0.1" 28 | declare -gr authors=("$(years_from 2021) Maddison Hellstrom ") 29 | declare -gr repo_short="b0o/$name" 30 | declare -gr repository="https://github.com/$repo_short" 31 | declare -gr issues="https://github.com/$repo_short/issues" 32 | declare -gr license="MIT" 33 | declare -gr license_url="https://mit-license.org" 34 | 35 | function usage() { 36 | cat </dev/null; then 146 | echo "$instance_pid" 147 | return 0 148 | fi 149 | return 1 150 | } 151 | 152 | function reload() { 153 | local -a entrypoints 154 | mapfile -t entrypoints < <(get_entrypoints) 155 | if [[ ${#entrypoints[@]} -eq 0 ]]; then 156 | entrypoints=("$@") 157 | fi 158 | if msg="$(tmux source-file "${entrypoints[@]}")"; then 159 | display_message "Reloaded $( 160 | printf '%s\n' "${entrypoints[@]}" | xargs -n1 basename | tr '\n' ',' | sed 's/,$/\n/; s/,/, /g' 161 | )" 162 | else 163 | display_message -d 0 "#[fg=white,bg=red,bold]ERROR: $msg" 164 | fi 165 | } 166 | 167 | function onexit() { 168 | local -i code=$? 169 | local -i entr_pid=$1 170 | display_message "$name exited with code $code" || true 171 | if [[ -v entr_pid && $entr_pid -gt 1 ]] && ps "$entr_pid" &>/dev/null; then 172 | kill "$entr_pid" || true 173 | fi 174 | tmux set-option -gu "@$name-pid" & 175 | return "$code" 176 | } 177 | 178 | function kill_instance() { 179 | local -i instance_pid 180 | if instance_pid="$(get_instance)"; then 181 | kill "$instance_pid" 182 | return $? 183 | fi 184 | echo "$name -k: kill failed: no instance found" >&2 185 | return 1 186 | } 187 | 188 | function get_status() { 189 | local -i instance_pid 190 | if instance_pid="$(get_instance)"; then 191 | echo "running: $instance_pid" 192 | return 0 193 | fi 194 | echo "not running" 195 | return 1 196 | } 197 | 198 | function main() { 199 | if ! [[ "${1:-}" =~ ^-[hvfksr]$ ]]; then 200 | "$self" -f "$@" &>/dev/null & 201 | disown 202 | exit $? 203 | fi 204 | 205 | local opt OPTARG 206 | local -i OPTIND 207 | while getopts "hvfksr:" opt "$@"; do 208 | case "$opt" in 209 | h) 210 | usage 211 | return 0 212 | ;; 213 | v) 214 | usage_version 215 | return 0 216 | ;; 217 | f) 218 | # Silently ignore -f 219 | ;; 220 | k) 221 | kill_instance 222 | return 223 | ;; 224 | s) 225 | get_status 226 | return 227 | ;; 228 | r) 229 | reload "$OPTARG" 230 | return 231 | ;; 232 | \?) 233 | return 1 234 | ;; 235 | esac 236 | done 237 | shift $((OPTIND - 1)) 238 | 239 | if get_instance &>/dev/null; then 240 | return 0 241 | fi 242 | 243 | command -v entr &>/dev/null || { 244 | echo "Command not found: entr" >&2 245 | display_message -d 0 "Failed to start $name: Command not found: entr" 246 | return 1 247 | } 248 | 249 | tmux set-option -g "@$name-pid" $$ 250 | 251 | # shellcheck disable=2016 252 | entr -np sh -c '"$0" -r "$1"' "$self" /_ <<<"$(printf '%s\n' "$(get_base_configs)" "$(get_user_configs)")" & 253 | # shellcheck disable=2064 254 | trap "onexit $!" EXIT 255 | wait 256 | } 257 | 258 | main "$@" 259 | --------------------------------------------------------------------------------