20 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
9 changed files with 432 additions and 17 deletions

View File

@ -2,6 +2,28 @@
### 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 ### v0.0.2, 2014-08-26
- saving a new session does not remove the previous one - saving a new session does not remove the previous one
- make the directory where sessions are stored configurable - make the directory where sessions are stored configurable

View File

@ -1,11 +1,39 @@
# Tmux Session Saver # Tmux Session Saver
Enables saving and restoring of tmux sessions. 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 ### Key bindings
- `prefix + M-s` - save - `prefix + Alt-s` - save
- `prefix + M-r` - restore - `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) ### Installation with [Tmux Plugin Manager](https://github.com/tmux-plugins/tpm) (recommended)
@ -36,5 +64,45 @@ Reload TMUX environment:
You should now be able to use the plugin. 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 ### License
[MIT](LICENSE.md) [MIT](LICENSE.md)

View File

@ -1,6 +1,6 @@
# configurable constants # configurable constants
default_sessions_dir="$HOME/.tmux/sessions" default_sessions_dir="$HOME/.tmux/sessions"
sessions_dir_option="@sessions-dir" sessions_dir_option="@session-saver-dir"
SUPPORTED_VERSION="1.9" SUPPORTED_VERSION="1.9"

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,12 +3,21 @@
CURRENT_DIR="$( cd "$( dirname "${BASH_SOURCE[0]}" )" && pwd )" CURRENT_DIR="$( cd "$( dirname "${BASH_SOURCE[0]}" )" && pwd )"
source "$CURRENT_DIR/helpers.sh" 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() { check_saved_session_exists() {
local saved_session="$(last_session_path)" local saved_session="$(last_session_path)"
if [ ! -f $saved_session ]; then if [ ! -f $saved_session ]; then
display_message "Saved session not found!" display_message "Saved tmux session not found!"
exit return 1
fi fi
} }
@ -32,6 +41,10 @@ tmux_socket() {
echo $TMUX | cut -d',' -f1 echo $TMUX | cut -d',' -f1
} }
remove_first_char() {
echo "$1" | cut -c2-
}
new_window() { new_window() {
local session_name="$1" local session_name="$1"
local window_number="$2" local window_number="$2"
@ -58,13 +71,15 @@ new_pane() {
local window_number="$2" local window_number="$2"
local window_name="$3" local window_name="$3"
local dir="$4" 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() { restore_pane() {
local pane="$1" local pane="$1"
echo "$pane" | echo "$pane" |
while IFS=$'\t' read session_name window_number window_name dir; do 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 if window_exists "$session_name" "$window_number"; then
new_pane "$session_name" "$window_number" "$window_name" "$dir" new_pane "$session_name" "$window_number" "$window_name" "$dir"
elif session_exists "$session_name"; then elif session_exists "$session_name"; then
@ -75,17 +90,77 @@ restore_pane() {
done 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() { restore_all_sessions() {
while read line; do while read line; do
if is_line_type "pane" "$line"; then
restore_pane "$line" 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) done < $(last_session_path)
display_message "Restored all Tmux sessions!"
} }
main() { main() {
if supported_tmux_version_ok; then if supported_tmux_version_ok && check_saved_session_exists; then
check_saved_session_exists start_spinner
restore_all_sessions 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 fi
} }
main main

View File

@ -4,27 +4,96 @@ CURRENT_DIR="$( cd "$( dirname "${BASH_SOURCE[0]}" )" && pwd )"
source "$CURRENT_DIR/helpers.sh" source "$CURRENT_DIR/helpers.sh"
dump_format() { pane_format() {
local delimiter=$'\t' local delimiter=$'\t'
local format local format
format+="pane"
format+="${delimiter}"
format+="#{session_name}" format+="#{session_name}"
format+="${delimiter}" format+="${delimiter}"
format+="#{window_index}" format+="#{window_index}"
format+="${delimiter}" 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+="${delimiter}"
format+="#{pane_current_path}" format+="#{pane_current_path}"
format+="${delimiter}"
format+="#{pane_active}"
format+="${delimiter}"
format+="#{pane_current_command}"
format+="${delimiter}"
format+="#{pane_pid}"
echo "$format" echo "$format"
} }
dump() { window_format() {
tmux list-panes -a -F "$(dump_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() { save_all_sessions() {
local session_path="$(session_path)" local session_path="$(session_path)"
mkdir -p "$(sessions_dir)" mkdir -p "$(sessions_dir)"
dump > $session_path dump_panes > $session_path
dump_windows >> $session_path
dump_state >> $session_path
ln -fs "$session_path" "$(last_session_path)" ln -fs "$session_path" "$(last_session_path)"
display_message "Saved all Tmux sessions!" display_message "Saved all Tmux sessions!"
} }

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