28 Commits

Author SHA1 Message Date
ee1ab0c728 v0.1.0 2014-08-28 13:20:45 +02:00
18f4d1099e Spin a spinner while tmux sessions are restored
Closes #16
2014-08-28 12:58:07 +02:00
655bdb9a75 Refactor checking if tmux session file exists 2014-08-28 12:45:48 +02:00
3da5d61b5b v0.0.5 2014-08-28 01:11:56 +02:00
5509256a02 Update readme 2014-08-28 01:09:55 +02:00
cde50d4d92 Command strategies; restore vim sessions
Closes #4
2014-08-28 00:43:31 +02:00
07bba0fde7 Update readme 2014-08-27 21:31:10 +02:00
1e945c2cac Enable selectively restoring processes
- user can restore all processes with ':all:'
- user can selectively restore wanted processes

Closes #13
2014-08-27 16:19:36 +02:00
7f50660918 User option for disabling pane process restoring 2014-08-27 13:12:32 +02:00
cbf58ac613 Restore all pane processes
Close #3
2014-08-27 00:28:35 +02:00
2edd3ff98d v0.0.4 2014-08-26 20:58:38 +02:00
bcad7cd1ea Bugfix: correct pane ordering in window 2014-08-26 20:57:53 +02:00
4d5c0a2a0d Improve active/alternate window restoring 2014-08-26 20:29:55 +02:00
8051fb9d36 Restore pane layout for each window
Close #2
2014-08-26 20:19:34 +02:00
a649614a20 v0.0.3 2014-08-26 19:18:24 +02:00
8166fa2602 Restore active and alternate window for each session
Closes #12
2014-08-26 19:16:51 +02:00
ecc42c5a56 Save and restore active pane
Active pane is restored for each window with multiple panes.

Closes #5
2014-08-26 18:54:39 +02:00
aa8f323b8b Improved handling of fields that can be empty 2014-08-26 17:51:56 +02:00
c78a38803a Bugfix: non-existing window names
Fixes #11
2014-08-26 17:28:40 +02:00
877780eb02 Save and restore current and alternate session
Closes #6
2014-08-26 17:27:46 +02:00
8ba7d6d873 v0.0.2 2014-08-26 16:08:09 +02:00
c993e9ff00 Add readme
Close #8
2014-08-26 16:07:04 +02:00
81ed0811b4 Error msg if saved session file doesn't exist
Close #9
2014-08-26 15:52:07 +02:00
732d53cede Support only Tmux v1.9 or greater
Closes #1
2014-08-26 15:47:31 +02:00
5c2853a55f Sessions directory is configurable
Close #10
2014-08-26 15:40:50 +02:00
70d78e8d73 Saving a session does not override the previous one
Close #7
2014-08-26 15:31:47 +02:00
1280e659d5 Remove debugging statements 2014-08-26 15:23:12 +02:00
3dce66acaa Update changelog 2014-08-26 14:58:53 +02:00
10 changed files with 595 additions and 18 deletions

View File

@ -1,4 +1,36 @@
# Changelog
# master
### master
### v0.1.0, 2014-08-28
- refactor checking if saved tmux session exists
- spinner while tmux sessions are restored
### v0.0.5, 2014-08-28
- restore pane processes
- user option for disabling pane process restoring
- enable whitelisting processes that will be restored
- expand readme with configuration options
- enable command strategies; enable restoring vim sessions
- update readme: explain restoring vim sessions
### v0.0.4, 2014-08-26
- restore pane layout for each window
- bugfix: correct pane ordering in a window
### v0.0.3, 2014-08-26
- save and restore current and alternate session
- fix a bug with non-existing window names
- restore active pane for each window that has multiple panes
- restore active and alternate window for each session
### v0.0.2, 2014-08-26
- saving a new session does not remove the previous one
- make the directory where sessions are stored configurable
- support only Tmux v1.9 or greater
- display a nice error message if saved session file does not exist
- added README
### v0.0.1, 2014-08-26
- started a project
- basic saving and restoring works

104
README.md
View File

@ -1,4 +1,108 @@
# Tmux Session Saver
Persists `tmux` environment across system restarts.
Tmux is great, except when you have to restart your computer. You loose all the
running programs, working directories, pane layouts etc.
There are helpful management tools out there, but they require initial
configuration and continuous updates as your workflow evolves or you start new
projects.
Enter `tmux-session-saver`: tmux persistence without configuration so there are
no interruptions in your workflow.
It will even (optionally) [restore vim sessions](#restoring-vim-sessions)!
### Key bindings
- `prefix + Alt-s` - save
- `prefix + Alt-r` - restore
### About
This plugin goes to great lengths to save and restore all the details from your
`tmux` environment. Here's what's been taken care of:
- all sessions, windows, panes and their order
- current working directory for each pane
- **exact pane layouts** within windows
- active and alternative session
- active and alternative window for each session
- active pane for each window
- programs running within a pane! More details in the [configuration section](#configuration).
- restoring vim sessions (optional). More details in
[restoring vim sessions](#restoring-vim-sessions).
Requirements / dependencies: `tmux 1.9` or higher, `pgrep`
### Installation with [Tmux Plugin Manager](https://github.com/tmux-plugins/tpm) (recommended)
Add plugin to the list of TPM plugins in `.tmux.conf`:
set -g @tpm_plugins " \
tmux-plugins/tpm \
tmux-plugins/tmux-session-saver \
"
Hit `prefix + I` to fetch the plugin and source it. You should now be able to
use the plugin.
### Manual Installation
Clone the repo:
$ git clone https://github.com/tmux-plugins/tmux-session-saver ~/clone/path
Add this line to the bottom of `.tmux.conf`:
run-shell ~/clone/path/session_saver.tmux
Reload TMUX environment:
# type this in terminal
$ tmux source-file ~/.tmux.conf
You should now be able to use the plugin.
### Configuration
By default, only a conservative list of programs is restored:
`vim emacs man less more tail top htop irssi`.
Open a github issue if you think some other program should be on the default list.
- Restore additional programs by putting the following in `.tmux.conf`:
set -g @session-saver-processes 'ssh telnet myprogram'
- Don't restore any programs:
set -g @session-saver-processes 'false'
- Restore **all** programs (be careful with this!):
set -g @session-saver-processes ':all:'
#### Restoring vim sessions
- save vim sessions - I recommend [tpope/vim-obsession](tpope/vim-obsession)
- in `.tmux.conf`:
set -g @session-saver-strategy-vim "session"
`tmux-session-saver` will now restore vim sessions if `Sessions.vim` file is
present.
### Reporting bugs and contributing
Code contributions are welcome!
If you find a bug please report it in the issues. When reporting a bug please
attach a file symlinked to `~/.tmux/sessions/last`.
### Credits
[Mislav Marohnić](https://github.com/mislav) - the idea for the plugin came from his
[tmux-session script](https://github.com/mislav/dotfiles/blob/master/bin/tmux-session).
### License
[MIT](LICENSE.md)

78
scripts/check_tmux_version.sh Executable file
View File

@ -0,0 +1,78 @@
#!/usr/bin/env bash
VERSION="$1"
UNSUPPORTED_MSG="$2"
get_tmux_option() {
local option=$1
local default_value=$2
local option_value=$(tmux show-option -gqv "$option")
if [ -z "$option_value" ]; then
echo "$default_value"
else
echo "$option_value"
fi
}
# Ensures a message is displayed for 5 seconds in tmux prompt.
# Does not override the 'display-time' tmux option.
display_message() {
local message="$1"
# display_duration defaults to 5 seconds, if not passed as an argument
if [ "$#" -eq 2 ]; then
local display_duration="$2"
else
local display_duration="5000"
fi
# saves user-set 'display-time' option
local saved_display_time=$(get_tmux_option "display-time" "750")
# sets message display time to 5 seconds
tmux set-option -gq display-time "$display_duration"
# displays message
tmux display-message "$message"
# restores original 'display-time' value
tmux set-option -gq display-time "$saved_display_time"
}
# this is used to get "clean" integer version number. Examples:
# `tmux 1.9` => `19`
# `1.9a` => `19`
get_digits_from_string() {
local string="$1"
local only_digits="$(echo "$string" | tr -dC '[:digit:]')"
echo "$only_digits"
}
tmux_version_int() {
local tmux_version_string=$(tmux -V)
echo "$(get_digits_from_string "$tmux_version_string")"
}
unsupported_version_message() {
if [ -n "$UNSUPPORTED_MSG" ]; then
echo "$UNSUPPORTED_MSG"
else
echo "Error, Tmux version unsupported! Please install Tmux version $VERSION or greater!"
fi
}
exit_if_unsupported_version() {
local current_version="$1"
local supported_version="$2"
if [ "$current_version" -lt "$supported_version" ]; then
display_message "$(unsupported_version_message)"
exit 1
fi
}
main() {
local supported_version_int="$(get_digits_from_string "$VERSION")"
local current_version_int="$(tmux_version_int)"
exit_if_unsupported_version "$current_version_int" "$supported_version_int"
}
main

View File

@ -1,3 +1,10 @@
# configurable constants
default_sessions_dir="$HOME/.tmux/sessions"
sessions_dir_option="@session-saver-dir"
SUPPORTED_VERSION="1.9"
# helper functions
get_tmux_option() {
local option="$1"
local default_value="$2"
@ -33,3 +40,23 @@ display_message() {
# restores original 'display-time' value
tmux set-option -gq display-time "$saved_display_time"
}
supported_tmux_version_ok() {
$CURRENT_DIR/check_tmux_version.sh "$SUPPORTED_VERSION"
}
# path helpers
sessions_dir() {
echo $(get_tmux_option "$sessions_dir_option" "$default_sessions_dir")
}
session_path() {
local timestamp="$(date +"%Y-%m-%dT%H:%M:%S")"
echo "$(sessions_dir)/tmux_session_${timestamp}.txt"
}
last_session_path() {
echo "$(sessions_dir)/last"
}

View File

@ -0,0 +1,121 @@
# default processes that are restored
default_proc_list_option="@session-saver-default-processes"
default_proc_list="vim emacs man less more tail top htop irssi"
# User defined processes that are restored
# 'false' - nothing is restored
# ':all:' - all processes are restored
#
# user defined list of programs that are restored:
# 'my_program foo another_program'
restore_processes_option="@session-saver-processes"
restore_processes=""
# Defines part of the user variable. Example usage:
# set -g @session-saver-strategy-vim "session"
restore_process_strategy_option="@session-saver-strategy-"
restore_pane_processes_enabled() {
local restore_processes="$(get_tmux_option "$restore_processes_option" "$restore_processes")"
if [ "$restore_processes" == "false" ]; then
return 1
else
return 0
fi
}
restore_pane_process() {
local pane_full_command="$1"
local session_name="$2"
local window_number="$3"
local pane_index="$4"
local dir="$5"
if _process_should_be_restored "$pane_full_command"; then
tmux switch-client -t "${session_name}:${window_number}"
tmux select-pane -t "$pane_index"
if _strategy_exists "$pane_full_command"; then
local strategy_file="$(_get_strategy_file "$pane_full_command")"
local strategy_command="$($strategy_file "$pane_full_command" "$dir")"
tmux send-keys "$strategy_command" "C-m"
# tmux send-keys "Strategy! $pane_full_command $strategy_file"
# tmux send-keys "Strategy! $strategy_command"
else
# just invoke the command
tmux send-keys "$pane_full_command" "C-m"
fi
fi
}
# private functions below
_process_should_be_restored() {
local pane_full_command="$1"
if _restore_all_processes; then
return 0
elif _process_on_the_restore_list "$pane_full_command"; then
return 0
else
return 1
fi
}
_restore_all_processes() {
local restore_processes="$(get_tmux_option "$restore_processes_option" "$restore_processes")"
if [ $restore_processes == ":all:" ]; then
return 0
else
return 1
fi
}
_process_on_the_restore_list() {
local pane_full_command="$1"
local restore_list="$(_restore_list)"
local proc
for proc in $restore_list; do
if [[ "$pane_full_command" =~ (^$proc) ]]; then
return 0
fi
done
return 1
}
_restore_list() {
local user_processes="$(get_tmux_option "$restore_processes_option" "$restore_processes")"
local default_processes="$(get_tmux_option "$default_proc_list_option" "$default_proc_list")"
if [ -z $user_processes ]; then
# user didn't define any processes
echo "$default_processes"
else
echo "$default_processes $user_processes"
fi
}
_strategy_exists() {
local pane_full_command="$1"
local strategy="$(_get_command_strategy "$pane_full_command")"
if [ -n "$strategy" ]; then # strategy set?
local strategy_file="$(_get_strategy_file "$pane_full_command")"
[ -e "$strategy_file" ] # strategy file exists?
else
return 1
fi
}
_get_command_strategy() {
local pane_full_command="$1"
local command="$(_just_command "$pane_full_command")"
get_tmux_option "${restore_process_strategy_option}${command}" ""
}
_just_command() {
echo "$1" | cut -d' ' -f1
}
_get_strategy_file() {
local pane_full_command="$1"
local strategy="$(_get_command_strategy "$pane_full_command")"
local command="$(_just_command "$pane_full_command")"
echo "$CURRENT_DIR/../strategies/${command}_${strategy}.sh"
}

View File

@ -3,6 +3,23 @@
CURRENT_DIR="$( cd "$( dirname "${BASH_SOURCE[0]}" )" && pwd )"
source "$CURRENT_DIR/helpers.sh"
source "$CURRENT_DIR/process_restore_helpers.sh"
source "$CURRENT_DIR/spinner_helpers.sh"
is_line_type() {
local line_type="$1"
local line="$2"
echo "$line" |
\grep -q "^$line_type"
}
check_saved_session_exists() {
local saved_session="$(last_session_path)"
if [ ! -f $saved_session ]; then
display_message "Saved tmux session not found!"
return 1
fi
}
window_exists() {
local session_name="$1"
@ -24,6 +41,10 @@ tmux_socket() {
echo $TMUX | cut -d',' -f1
}
remove_first_char() {
echo "$1" | cut -c2-
}
new_window() {
local session_name="$1"
local window_number="$2"
@ -50,35 +71,96 @@ new_pane() {
local window_number="$2"
local window_name="$3"
local dir="$4"
tmux split-window -d -t "${session_name}:${window_number}" -c "$dir"
tmux split-window -t "${session_name}:${window_number}" -c "$dir"
}
restore_pane() {
local pane="$1"
echo "$pane" |
while IFS=$'\t' read session_name window_number window_name dir; do
# echo "$session_name - $window_number - $window_name - $dir"
while IFS=$'\t' read line_type session_name window_number window_name window_active window_flags pane_index dir pane_active pane_command pane_full_command; do
window_name="$(remove_first_char "$window_name")"
pane_full_command="$(remove_first_char "$pane_full_command")"
if window_exists "$session_name" "$window_number"; then
echo "new pane $session_name $window_number"
new_pane "$session_name" "$window_number" "$window_name" "$dir"
elif session_exists "$session_name"; then
echo "new window $session_name $window_number"
new_window "$session_name" "$window_number" "$window_name" "$dir"
else
echo "new session $session_name"
new_session "$session_name" "$window_number" "$window_name" "$dir"
fi
done
}
restore_state() {
local state="$1"
echo "$state" |
while IFS=$'\t' read line_type client_session client_last_session; do
tmux switch-client -t "$client_last_session"
tmux switch-client -t "$client_session"
done
}
restore_all_sessions() {
while read line; do
restore_pane "$line"
done < $HOME/.tmux/session
display_message "Restored all Tmux sessions!"
if is_line_type "pane" "$line"; then
restore_pane "$line"
fi
done < $(last_session_path)
}
restore_all_pane_processes() {
if restore_pane_processes_enabled; then
local pane_full_command
awk 'BEGIN { FS="\t"; OFS="\t" } /^pane/ && $11 !~ "^:$" { print $2, $3, $7, $8, $11; }' $(last_session_path) |
while IFS=$'\t' read session_name window_number pane_index dir pane_full_command; do
pane_full_command="$(remove_first_char "$pane_full_command")"
restore_pane_process "$pane_full_command" "$session_name" "$window_number" "$pane_index" "$dir"
done
fi
}
restore_pane_layout_for_each_window() {
\grep '^window' $(last_session_path) |
while IFS=$'\t' read line_type session_name window_number window_active window_flags window_layout; do
tmux select-layout -t "${session_name}:${window_number}" "$window_layout"
done
}
restore_active_pane_for_each_window() {
awk 'BEGIN { FS="\t"; OFS="\t" } /^pane/ && $7 != 0 && $9 == 1 { print $2, $3, $7; }' $(last_session_path) |
while IFS=$'\t' read session_name window_number active_pane; do
tmux switch-client -t "${session_name}:${window_number}"
tmux select-pane -t "$active_pane"
done
}
restore_active_and_alternate_windows() {
awk 'BEGIN { FS="\t"; OFS="\t" } /^window/ && $5 ~ /[*-]/ { print $2, $4, $3; }' $(last_session_path) |
sort -u |
while IFS=$'\t' read session_name active_window window_number; do
tmux switch-client -t "${session_name}:${window_number}"
done
}
restore_active_and_alternate_sessions() {
while read line; do
if is_line_type "state" "$line"; then
restore_state "$line"
fi
done < $(last_session_path)
}
main() {
restore_all_sessions
if supported_tmux_version_ok && check_saved_session_exists; then
start_spinner
restore_all_sessions
restore_all_pane_processes
restore_pane_layout_for_each_window >/dev/null 2>&1
# below functions restore exact cursor positions
restore_active_pane_for_each_window
restore_active_and_alternate_windows
restore_active_and_alternate_sessions
stop_spinner
display_message "Restored all Tmux sessions!"
fi
}
main

View File

@ -4,30 +4,103 @@ CURRENT_DIR="$( cd "$( dirname "${BASH_SOURCE[0]}" )" && pwd )"
source "$CURRENT_DIR/helpers.sh"
dump_format() {
pane_format() {
local delimiter=$'\t'
local format
format+="pane"
format+="${delimiter}"
format+="#{session_name}"
format+="${delimiter}"
format+="#{window_index}"
format+="${delimiter}"
format+="#{window_name}"
format+=":#{window_name}"
format+="${delimiter}"
format+="#{window_active}"
format+="${delimiter}"
format+=":#{window_flags}"
format+="${delimiter}"
format+="#{pane_index}"
format+="${delimiter}"
format+="#{pane_current_path}"
format+="${delimiter}"
format+="#{pane_active}"
format+="${delimiter}"
format+="#{pane_current_command}"
format+="${delimiter}"
format+="#{pane_pid}"
echo "$format"
}
dump() {
tmux list-panes -a -F "$(dump_format)"
window_format() {
local delimiter=$'\t'
local format
format+="window"
format+="${delimiter}"
format+="#{session_name}"
format+="${delimiter}"
format+="#{window_index}"
format+="${delimiter}"
format+="#{window_active}"
format+="${delimiter}"
format+=":#{window_flags}"
format+="${delimiter}"
format+="#{window_layout}"
echo "$format"
}
state_format() {
local delimiter=$'\t'
local format
format+="state"
format+="${delimiter}"
format+="#{client_session}"
format+="${delimiter}"
format+="#{client_last_session}"
echo "$format"
}
dump_panes_raw() {
tmux list-panes -a -F "$(pane_format)"
}
pane_full_command() {
pane_pid="$1"
\pgrep -lf -P "$pane_pid" |
cut -d' ' -f2-
}
# translates pane pid to process command running inside a pane
dump_panes() {
local full_command
local d=$'\t' # delimiter
dump_panes_raw |
while IFS=$'\t' read line_type session_name window_number window_name window_active window_flags pane_index dir pane_active pane_command pane_pid; do
full_command="$(pane_full_command $pane_pid)"
echo "${line_type}${d}${session_name}${d}${window_number}${d}${window_name}${d}${window_active}${d}${window_flags}${d}${pane_index}${d}${dir}${d}${pane_active}${d}${pane_command}${d}:${full_command}"
done
}
dump_windows() {
tmux list-windows -a -F "$(window_format)"
}
dump_state() {
tmux display-message -p "$(state_format)"
}
save_all_sessions() {
mkdir -p $HOME/.tmux
dump > $HOME/.tmux/session
local session_path="$(session_path)"
mkdir -p "$(sessions_dir)"
dump_panes > $session_path
dump_windows >> $session_path
dump_state >> $session_path
ln -fs "$session_path" "$(last_session_path)"
display_message "Saved all Tmux sessions!"
}
main() {
save_all_sessions
if supported_tmux_version_ok; then
save_all_sessions
fi
}
main

View File

@ -0,0 +1,8 @@
start_spinner() {
$CURRENT_DIR/tmux_spinner.sh "Restoring sessions..." "Restored all Tmux sessions!" &
export SPINNER_PID=$!
}
stop_spinner() {
kill $SPINNER_PID
}

29
scripts/tmux_spinner.sh Executable file
View File

@ -0,0 +1,29 @@
#!/usr/bin/env bash
# This script shows tmux spinner with a message. It is intended to be running
# as a background process which should be `kill`ed at the end.
#
# Example usage:
#
# ./tmux_spinner.sh "Working..." "End message!" &
# SPINNER_PID=$!
# ..
# .. execute commands here
# ..
# kill $SPINNER_PID # Stops spinner and displays 'End message!'
MESSAGE="$1"
END_MESSAGE="$2"
SPIN='-\|/'
trap "tmux display-message $END_MESSAGE; exit" SIGINT SIGTERM
main() {
local i=0
while true; do
i=$(( (i+1) %4 ))
tmux display-message " ${SPIN:$i:1} $MESSAGE"
sleep 0.1
done
}
main

23
strategies/vim_session.sh Executable file
View File

@ -0,0 +1,23 @@
#!/usr/bin/env bash
# "vim session strategy"
#
# Restores a vim session from 'Session.vim' file, if it exists.
# If 'Session.vim' does not exist, it falls back to invoking the original
# command.
ORIGINAL_COMMAND="$1"
DIRECTORY="$2"
vim_session_file_exists() {
[ -e "${DIRECTORY}/Session.vim" ]
}
main() {
if vim_session_file_exists; then
echo "vim -S"
else
echo "$ORIGINAL_COMMAND"
fi
}
main