18 Commits

Author SHA1 Message Date
eca38175ff v0.2.0 2014-08-29 00:19:22 +02:00
b8b87a6e29 Improve irb restore strategy
Fixes #21
2014-08-29 00:17:02 +02:00
8cbc18e130 Move contributing guidelines to a separate file 2014-08-28 23:48:21 +02:00
cfe8e7979b Restoring programs with arguments; improve process matching
Closes #20, closes #19
2014-08-28 23:48:04 +02:00
093627ce0a Update readme 2014-08-28 16:43:32 +02:00
0da279e4cd "vi, irb, pry" are restored by default 2014-08-28 14:35:49 +02:00
884a5e9c19 Improve default restored program command matching 2014-08-28 13:53:09 +02:00
8cb5b21e2f Fix #18: improve vim 'session' strategy 2014-08-28 13:43:04 +02:00
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
13 changed files with 399 additions and 22 deletions

View File

@ -2,6 +2,25 @@
### master
### v0.2.0, 2014-08-29
- bugfix: with vim 'session' strategy, if the session file does not exist - make
sure vim does not contain `-S` flag
- enable restoring programs with arguments (e.g. "rails console") and also
processes that contain program name
- improve `irb` restore strategy
### 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

12
CONTRIBUTING.md Normal file
View File

@ -0,0 +1,12 @@
### Contributing
Code contributions are welcome!
### Reporting a bug
If you find a bug please report it in the issues. When reporting a bug please
attach:
- a file symlinked to `~/.tmux/sessions/last`.
- your `.tmux.conf`
- if you're getting an error paste it to a [gist](https://gist.github.com/) and
link it in the issue

View File

@ -1,11 +1,39 @@
# 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
- `prefix + M-s` - save
- `prefix + M-r` - restore
- `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)
@ -36,5 +64,51 @@ Reload TMUX environment:
You should now be able to use the plugin.
### Configuration
Only a conservative list of programs is restored by default:
`vi vim emacs man less more tail top htop irssi irb pry "~rails console"`.
Open a github issue if you think some other program should be on the default list.
- Restore additional programs with the setting in `.tmux.conf`:
set -g @session-saver-processes 'ssh psql mysql sqlite3'
- Programs with arguments should be double quoted:
set -g @session-saver-processes 'some_program "git log"'
- Start with tilde to restore a program whose process contains target name:
set -g @session-saver-processes 'some_program "~rails server"'
- 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
Both contributing and bug reports are welcome. Please check out
[contributing guidelines](CONTRIBUTING.md).
### 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)

View File

@ -1,6 +1,6 @@
# configurable constants
default_sessions_dir="$HOME/.tmux/sessions"
sessions_dir_option="@sessions-dir"
sessions_dir_option="@session-saver-dir"
SUPPORTED_VERSION="1.9"
@ -46,6 +46,10 @@ supported_tmux_version_ok() {
$CURRENT_DIR/check_tmux_version.sh "$SUPPORTED_VERSION"
}
remove_first_char() {
echo "$1" | cut -c2-
}
# path helpers
sessions_dir() {

View File

@ -0,0 +1,118 @@
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"
# TODO: make this work without eval
eval set $(_restore_list)
local proc
for proc in "$@"; do
if _proc_starts_with_tildae "$proc"; then
proc="$(remove_first_char "$proc")"
# regex matching the command makes sure `$proc` string is somewhere the command string
if [[ "$pane_full_command" =~ ($proc) ]]; then
return 0
fi
else
# regex matching the command makes sure process is a "word"
if [[ "$pane_full_command" =~ (^${proc} ) ]] || [[ "$pane_full_command" =~ (^${proc}$) ]]; then
return 0
fi
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
}
_proc_starts_with_tildae() {
[[ "$1" =~ (^~) ]]
}
_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

@ -2,7 +2,10 @@
CURRENT_DIR="$( cd "$( dirname "${BASH_SOURCE[0]}" )" && pwd )"
source "$CURRENT_DIR/variables.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"
@ -14,8 +17,8 @@ is_line_type() {
check_saved_session_exists() {
local saved_session="$(last_session_path)"
if [ ! -f $saved_session ]; then
display_message "Saved session not found!"
exit
display_message "Saved tmux session not found!"
return 1
fi
}
@ -39,10 +42,6 @@ tmux_socket() {
echo $TMUX | cut -d',' -f1
}
remove_first_char() {
echo "$1" | cut -c2-
}
new_window() {
local session_name="$1"
local window_number="$2"
@ -75,8 +74,9 @@ new_pane() {
restore_pane() {
local pane="$1"
echo "$pane" |
while IFS=$'\t' read line_type session_name window_number window_name window_active window_flags pane_index dir pane_active; do
window_name="$(remove_first_char $window_name)"
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
new_pane "$session_name" "$window_number" "$window_name" "$dir"
elif session_exists "$session_name"; then
@ -104,6 +104,17 @@ restore_all_sessions() {
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
@ -136,13 +147,16 @@ restore_active_and_alternate_sessions() {
}
main() {
if supported_tmux_version_ok; then
check_saved_session_exists
if supported_tmux_version_ok && check_saved_session_exists; then
start_spinner
restore_all_sessions
restore_pane_layout_for_each_window
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
}

View File

@ -24,6 +24,10 @@ pane_format() {
format+="#{pane_current_path}"
format+="${delimiter}"
format+="#{pane_active}"
format+="${delimiter}"
format+="#{pane_current_command}"
format+="${delimiter}"
format+="#{pane_pid}"
echo "$format"
}
@ -55,10 +59,27 @@ state_format() {
echo "$format"
}
dump_panes() {
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)"
}

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
scripts/variables.sh Normal file
View File

@ -0,0 +1,23 @@
# key bindings
default_save_key="M-s"
save_option="@session-saver-save"
default_restore_key="M-r"
restore_option="@session-saver-restore"
# default processes that are restored
default_proc_list_option="@session-saver-default-processes"
default_proc_list='vi vim emacs man less more tail top htop irssi irb pry "~rails console"'
# 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-"

View File

@ -2,14 +2,9 @@
CURRENT_DIR="$( cd "$( dirname "${BASH_SOURCE[0]}" )" && pwd )"
source "$CURRENT_DIR/scripts/variables.sh"
source "$CURRENT_DIR/scripts/helpers.sh"
default_save_key="M-s"
save_option="@session-saver-save"
default_restore_key="M-r"
restore_option="@session-saver-restore"
set_save_bindings() {
local key_bindings=$(get_tmux_option "$save_option" "$default_save_key")
local key
@ -26,8 +21,13 @@ set_restore_bindings() {
done
}
set_default_strategies() {
tmux set-option -g "${restore_process_strategy_option}irb" "default_strategy"
}
main() {
set_save_bindings
set_restore_bindings
set_default_strategies
}
main

View File

@ -0,0 +1,23 @@
#!/usr/bin/env bash
# "irb default strategy"
#
# Example irb process with junk variables:
# irb RBENV_VERSION=1.9.3-p429 GREP_COLOR=34;47 TERM_PROGRAM=Apple_Terminal
#
# When executed, the above will fail. This strategy handles that.
ORIGINAL_COMMAND="$1"
DIRECTORY="$2"
original_command_wo_junk_vars() {
echo "$ORIGINAL_COMMAND" |
sed 's/RBENV_VERSION[^ ]*//' |
sed 's/GREP_COLOR[^ ]*//' |
sed 's/TERM_PROGRAM[^ ]*//'
}
main() {
echo "$(original_command_wo_junk_vars)"
}
main

32
strategies/vim_session.sh Executable file
View File

@ -0,0 +1,32 @@
#!/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 (withouth the `-S` flag).
ORIGINAL_COMMAND="$1"
DIRECTORY="$2"
vim_session_file_exists() {
[ -e "${DIRECTORY}/Session.vim" ]
}
original_command_contains_session_flag() {
[[ "$ORIGINAL_COMMAND" =~ "-S" ]]
}
main() {
if vim_session_file_exists; then
echo "vim -S"
elif original_command_contains_session_flag; then
# Session file does not exist, yet the orignal vim command contains
# session flag `-S`. This will cause an error, so we're falling back to
# starting plain vim.
echo "vim"
else
echo "$ORIGINAL_COMMAND"
fi
}
main