18 Commits

Author SHA1 Message Date
beb54b62fb v0.4.0 2014-08-29 19:11:21 +02:00
bd095e739d Change plugin name and all the variables 2014-08-29 18:59:14 +02:00
e2e55c6faa v0.3.0 2014-08-29 17:21:27 +02:00
cedd1292c1 Restore window zoom state
Close #25
2014-08-29 17:04:00 +02:00
05cf790493 Enable restoring more panes per window
Closes #24
2014-08-29 16:16:21 +02:00
f9ef86d604 Do not restore processes within existing panes
Closes #23
2014-08-29 15:50:18 +02:00
9a6e4a1a2c Make pane restorations idempotent 2014-08-29 12:49:06 +02:00
bd13c9bae8 Clean out comments 2014-08-29 01:04:50 +02:00
87b2d75794 Update readme 2014-08-29 01:04:03 +02:00
54f47a4015 Properly restore pane with top program
Fixes #17
2014-08-29 00:42:11 +02:00
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
12 changed files with 246 additions and 100 deletions

View File

@ -2,6 +2,26 @@
### master
### v0.4.0, 2014-08-29
- change plugin name to `tmux-resurrect`. Change all the variable names.
### v0.3.0, 2014-08-29
- bugfix: when top is running the pane `$PWD` can't be saved. This was causing
issues during the restore and is now fixed.
- restoring sessions multiple times messes up the whole environment - new panes
are all around. This is now fixed - pane restorations are now idempotent.
- if pane exists from before session restore - do not restore the process within
it. This makes the restoration process even more idempotent.
- more panes within a window can now be restored
- restore window zoom state
### 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

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/resurrect/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,17 +1,18 @@
# Tmux Session Saver
# Tmux Resurrect
Persists `tmux` environment across system restarts.
Restore `tmux` environment after a system restart.
Tmux is great, except when you have to restart your computer. You loose all the
Tmux is great, except when you have to restart the 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.
`tmux-resurrect` saves all the little details from tmux environment so it
can be completely restored after a system restart. No configuration is required.
You should feel like you never quit tmux.
It will even (optionally) [restore vim sessions](#restoring-vim-sessions)!
It even (optionally) [restores vim sessions](#restoring-vim-sessions)!
### Key bindings
@ -28,8 +29,10 @@ This plugin goes to great lengths to save and restore all the details from your
- **exact pane layouts** within windows
- active and alternative session
- active and alternative window for each session
- windows with focus
- active pane for each window
- programs running within a pane! More details in the [configuration section](#configuration).
- 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).
@ -41,7 +44,7 @@ Add plugin to the list of TPM plugins in `.tmux.conf`:
set -g @tpm_plugins " \
tmux-plugins/tpm \
tmux-plugins/tmux-session-saver \
tmux-plugins/tmux-resurrect \
"
Hit `prefix + I` to fetch the plugin and source it. You should now be able to
@ -51,11 +54,11 @@ use the plugin.
Clone the repo:
$ git clone https://github.com/tmux-plugins/tmux-session-saver ~/clone/path
$ git clone https://github.com/tmux-plugins/tmux-resurrect ~/clone/path
Add this line to the bottom of `.tmux.conf`:
run-shell ~/clone/path/session_saver.tmux
run-shell ~/clone/path/resurrect.tmux
Reload TMUX environment:
@ -66,38 +69,46 @@ 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`.
Configuration is not required - but it enables extra features.
Only a conservative list of programs is restored by default:<br/>
`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 by putting the following in `.tmux.conf`:
- Restore additional programs with the setting in `.tmux.conf`:
set -g @session-saver-processes 'ssh telnet myprogram'
set -g @resurrect-processes 'ssh psql mysql sqlite3'
- Programs with arguments should be double quoted:
set -g @resurrect-processes 'some_program "git log"'
- Start with tilde to restore a program whose process contains target name:
set -g @resurrect-processes 'some_program "~rails server"'
- Don't restore any programs:
set -g @session-saver-processes 'false'
set -g @resurrect-processes 'false'
- Restore **all** programs (be careful with this!):
set -g @session-saver-processes ':all:'
set -g @resurrect-processes ':all:'
#### Restoring vim sessions
- save vim sessions - I recommend [tpope/vim-obsession](tpope/vim-obsession)
- save vim sessions. I recommend [tpope/vim-obsession](tpope/vim-obsession).
- in `.tmux.conf`:
set -g @session-saver-strategy-vim "session"
set -g @resurrect-strategy-vim "session"
`tmux-session-saver` will now restore vim sessions if `Sessions.vim` file is
`tmux-resurrect` 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`.
Both contributing and bug reports are welcome. Please check out
[contributing guidelines](CONTRIBUTING.md).
### Credits

View File

@ -2,19 +2,14 @@
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
for key in $key_bindings; do
tmux bind-key "$key" run-shell "$CURRENT_DIR/scripts/session_saver.sh"
tmux bind-key "$key" run-shell "$CURRENT_DIR/scripts/save.sh"
done
}
@ -22,12 +17,17 @@ set_restore_bindings() {
local key_bindings=$(get_tmux_option "$restore_option" "$default_restore_key")
local key
for key in $key_bindings; do
tmux bind-key "$key" run-shell "$CURRENT_DIR/scripts/session_restorer.sh"
tmux bind-key "$key" run-shell "$CURRENT_DIR/scripts/restore.sh"
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

@ -1,6 +1,5 @@
# configurable constants
default_sessions_dir="$HOME/.tmux/sessions"
sessions_dir_option="@session-saver-dir"
default_resurrect_dir="$HOME/.tmux/resurrect"
resurrect_dir_option="@resurrect-dir"
SUPPORTED_VERSION="1.9"
@ -46,17 +45,21 @@ supported_tmux_version_ok() {
$CURRENT_DIR/check_tmux_version.sh "$SUPPORTED_VERSION"
}
remove_first_char() {
echo "$1" | cut -c2-
}
# path helpers
sessions_dir() {
echo $(get_tmux_option "$sessions_dir_option" "$default_sessions_dir")
resurrect_dir() {
echo $(get_tmux_option "$resurrect_dir_option" "$default_resurrect_dir")
}
session_path() {
resurrect_file_path() {
local timestamp="$(date +"%Y-%m-%dT%H:%M:%S")"
echo "$(sessions_dir)/tmux_session_${timestamp}.txt"
echo "$(resurrect_dir)/tmux_resurrect_${timestamp}.txt"
}
last_session_path() {
echo "$(sessions_dir)/last"
last_resurrect_file() {
echo "$(resurrect_dir)/last"
}

View File

@ -1,20 +1,3 @@
# 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
@ -30,7 +13,7 @@ restore_pane_process() {
local window_number="$3"
local pane_index="$4"
local dir="$5"
if _process_should_be_restored "$pane_full_command"; then
if _process_should_be_restored "$pane_full_command" "$session_name" "$window_number" "$pane_index"; then
tmux switch-client -t "${session_name}:${window_number}"
tmux select-pane -t "$pane_index"
@ -38,8 +21,6 @@ restore_pane_process() {
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"
@ -51,7 +32,14 @@ restore_pane_process() {
_process_should_be_restored() {
local pane_full_command="$1"
if _restore_all_processes; then
local session_name="$2"
local window_number="$3"
local pane_index="$4"
if is_pane_registered_as_existing "$session_name" "$window_number" "$pane_index"; then
# Scenario where pane existed before restoration, so we're not
# restoring the proces either.
return 1
elif _restore_all_processes; then
return 0
elif _process_on_the_restore_list "$pane_full_command"; then
return 0
@ -71,12 +59,22 @@ _restore_all_processes() {
_process_on_the_restore_list() {
local pane_full_command="$1"
local restore_list="$(_restore_list)"
# TODO: make this work without eval
eval set $(_restore_list)
local proc
for proc in $restore_list; do
if [[ "$pane_full_command" =~ (^$proc) ]]; then
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
}
@ -92,6 +90,10 @@ _restore_list() {
fi
}
_proc_starts_with_tildae() {
[[ "$1" =~ (^~) ]]
}
_strategy_exists() {
local pane_full_command="$1"
local strategy="$(_get_command_strategy "$pane_full_command")"

View File

@ -2,10 +2,17 @@
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"
# Global variable.
# Used during the restore: if a pane already exists from before, it is
# saved in the array in this variable. Later, process running in existing pane
# is also not restored. That makes the restoration process more idempotent.
EXISTING_PANES_VAR=""
is_line_type() {
local line_type="$1"
local line="$2"
@ -14,13 +21,38 @@ is_line_type() {
}
check_saved_session_exists() {
local saved_session="$(last_session_path)"
if [ ! -f $saved_session ]; then
display_message "Saved tmux session not found!"
local resurrect_file="$(last_resurrect_file)"
if [ ! -f $resurrect_file ]; then
display_message "Tmux resurrect file not found!"
return 1
fi
}
pane_exists() {
local session_name="$1"
local window_number="$2"
local pane_index="$3"
tmux list-panes -t "${session_name}:${window_number}" -F "#{pane_index}" 2>/dev/null |
\grep -q "^$pane_index$"
}
register_existing_pane() {
local session_name="$1"
local window_number="$2"
local pane_index="$3"
local pane_custom_id="${session_name}:${window_number}:${pane_index}"
local delimiter=$'\t'
EXISTING_PANES_VAR="${EXISTING_PANES_VAR}${delimiter}${pane_custom_id}"
}
is_pane_registered_as_existing() {
local session_name="$1"
local window_number="$2"
local pane_index="$3"
local pane_custom_id="${session_name}:${window_number}:${pane_index}"
[[ "$EXISTING_PANES_VAR" =~ "$pane_custom_id" ]]
}
window_exists() {
local session_name="$1"
local window_number="$2"
@ -41,10 +73,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"
@ -71,23 +99,28 @@ new_pane() {
local window_number="$2"
local window_name="$3"
local dir="$4"
tmux split-window -t "${session_name}:${window_number}" -c "$dir"
tmux split-window -t "${session_name}:${window_number}" -c "$dir" -h
tmux resize-pane -t "${session_name}:${window_number}" -L "999"
}
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 pane_command pane_full_command; do
dir="$(remove_first_char "$dir")"
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 pane_exists "$session_name" "$window_number" "$pane_index"; then
# Pane exists, no need to create it!
# Pane existence is registered. Later, it's process also isn't restored.
register_existing_pane "$session_name" "$window_number" "$pane_index"
elif window_exists "$session_name" "$window_number"; then
new_pane "$session_name" "$window_number" "$window_name" "$dir"
elif session_exists "$session_name"; then
new_window "$session_name" "$window_number" "$window_name" "$dir"
else
new_session "$session_name" "$window_number" "$window_name" "$dir"
fi
done
done < <(echo "$pane")
}
restore_state() {
@ -99,19 +132,20 @@ restore_state() {
done
}
restore_all_sessions() {
restore_all_panes() {
while read line; do
if is_line_type "pane" "$line"; then
restore_pane "$line"
fi
done < $(last_session_path)
done < $(last_resurrect_file)
}
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) |
awk 'BEGIN { FS="\t"; OFS="\t" } /^pane/ && $11 !~ "^:$" { print $2, $3, $7, $8, $11; }' $(last_resurrect_file) |
while IFS=$'\t' read session_name window_number pane_index dir pane_full_command; do
dir="$(remove_first_char "$dir")"
pane_full_command="$(remove_first_char "$pane_full_command")"
restore_pane_process "$pane_full_command" "$session_name" "$window_number" "$pane_index" "$dir"
done
@ -119,22 +153,29 @@ restore_all_pane_processes() {
}
restore_pane_layout_for_each_window() {
\grep '^window' $(last_session_path) |
\grep '^window' $(last_resurrect_file) |
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) |
awk 'BEGIN { FS="\t"; OFS="\t" } /^pane/ && $9 == 1 { print $2, $3, $7; }' $(last_resurrect_file) |
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_zoomed_windows() {
awk 'BEGIN { FS="\t"; OFS="\t" } /^window/ && $5 ~ /Z/ { print $2, $3; }' $(last_resurrect_file) |
while IFS=$'\t' read session_name window_number; do
tmux resize-pane -t "${session_name}:${window_number}" -Z
done
}
restore_active_and_alternate_windows() {
awk 'BEGIN { FS="\t"; OFS="\t" } /^window/ && $5 ~ /[*-]/ { print $2, $4, $3; }' $(last_session_path) |
awk 'BEGIN { FS="\t"; OFS="\t" } /^window/ && $5 ~ /[*-]/ { print $2, $4, $3; }' $(last_resurrect_file) |
sort -u |
while IFS=$'\t' read session_name active_window window_number; do
tmux switch-client -t "${session_name}:${window_number}"
@ -146,21 +187,22 @@ restore_active_and_alternate_sessions() {
if is_line_type "state" "$line"; then
restore_state "$line"
fi
done < $(last_session_path)
done < $(last_resurrect_file)
}
main() {
if supported_tmux_version_ok && check_saved_session_exists; then
start_spinner
restore_all_sessions
restore_all_pane_processes
restore_all_panes
restore_pane_layout_for_each_window >/dev/null 2>&1
restore_all_pane_processes
# below functions restore exact cursor positions
restore_active_pane_for_each_window
restore_zoomed_windows
restore_active_and_alternate_windows
restore_active_and_alternate_sessions
stop_spinner
display_message "Restored all Tmux sessions!"
display_message "Tmux restore complete!"
fi
}
main

View File

@ -2,6 +2,7 @@
CURRENT_DIR="$( cd "$( dirname "${BASH_SOURCE[0]}" )" && pwd )"
source "$CURRENT_DIR/scripts/variables.sh"
source "$CURRENT_DIR/helpers.sh"
pane_format() {
@ -21,7 +22,7 @@ pane_format() {
format+="${delimiter}"
format+="#{pane_index}"
format+="${delimiter}"
format+="#{pane_current_path}"
format+=":#{pane_current_path}"
format+="${delimiter}"
format+="#{pane_active}"
format+="${delimiter}"
@ -88,19 +89,19 @@ dump_state() {
tmux display-message -p "$(state_format)"
}
save_all_sessions() {
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!"
save_all() {
local resurrect_file_path="$(resurrect_file_path)"
mkdir -p "$(resurrect_dir)"
dump_panes > $resurrect_file_path
dump_windows >> $resurrect_file_path
dump_state >> $resurrect_file_path
ln -fs "$resurrect_file_path" "$(last_resurrect_file)"
display_message "Tmux environment saved!"
}
main() {
if supported_tmux_version_ok; then
save_all_sessions
save_all
fi
}
main

View File

@ -1,5 +1,5 @@
start_spinner() {
$CURRENT_DIR/tmux_spinner.sh "Restoring sessions..." "Restored all Tmux sessions!" &
$CURRENT_DIR/tmux_spinner.sh "Restoring tmux..." "Tmux restore complete!" &
export SPINNER_PID=$!
}

23
scripts/variables.sh Normal file
View File

@ -0,0 +1,23 @@
# key bindings
default_save_key="M-s"
save_option="@resurrect-save"
default_restore_key="M-r"
restore_option="@resurrect-restore"
# default processes that are restored
default_proc_list_option="@resurrect-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="@resurrect-processes"
restore_processes=""
# Defines part of the user variable. Example usage:
# set -g @resurrect-strategy-vim "session"
restore_process_strategy_option="@resurrect-strategy-"

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

View File

@ -4,7 +4,7 @@
#
# 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.
# command (without the `-S` flag).
ORIGINAL_COMMAND="$1"
DIRECTORY="$2"
@ -13,9 +13,18 @@ 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 original 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