From 319c129cbde50a38ce53745203d193ab2e741ffb Mon Sep 17 00:00:00 2001 From: Pokey Rule Date: Mon, 17 Jun 2019 17:01:49 +0100 Subject: [PATCH] Add support for linked windows --- scripts/restore.sh | 29 +++++++++++++++++++ scripts/save.sh | 72 +++++++++++++++++++++++++++++++++++++++++++--- 2 files changed, 97 insertions(+), 4 deletions(-) diff --git a/scripts/restore.sh b/scripts/restore.sh index 4104af3..750028d 100755 --- a/scripts/restore.sh +++ b/scripts/restore.sh @@ -272,6 +272,34 @@ restore_pane_layout_for_each_window() { done } +restore_linked_windows() { + \grep '^link' $(last_resurrect_file) | + while IFS=$d read line_type session_name window_number window_source; do + if session_exists "$session_name"; then + tmux link-window -s "${window_source}" -t "${session_name}:${window_number}" + else + # Create session if it doesn't exist. This situation occurs + # when a session only contains linked windows. Otherwise the + # session will have been created at some point while + # restoring the panes. + TMUX="" tmux -S "$(tmux_socket)" new-session -d -s "$session_name" + + local created_window_num="$(first_window_num)" + # If the window number we created is the same as our target + # window, we move it up by 1 + if [ $created_window_num -eq $window_number ]; then + original_window_num=$created_window_num + let created_window_num=$original_window_num+1 + tmux move-window -s "${session_name}:${original_window_num}" -t "${session_name}:${created_window_num}" + fi + tmux link-window -s "${window_source}" -t "${session_name}:${window_number}" + # We keep the window around to the end so that the session + # doesn't disappear + tmux kill-window -t "${session_name}:${created_window_num}" + fi + done +} + restore_shell_history() { awk 'BEGIN { FS="\t"; OFS="\t" } /^pane/ { print $2, $3, $7, $10; }' $(last_resurrect_file) | while IFS=$d read session_name window_number pane_index pane_command; do @@ -359,6 +387,7 @@ main() { restore_active_pane_for_each_window restore_zoomed_windows restore_grouped_sessions # also restores active and alt windows for grouped sessions + restore_linked_windows restore_active_and_alternate_windows restore_active_and_alternate_sessions execute_hook "post-restore-all" diff --git a/scripts/save.sh b/scripts/save.sh index 9d80af7..28c26f6 100755 --- a/scripts/save.sh +++ b/scripts/save.sh @@ -50,6 +50,8 @@ pane_format() { format+="#{pane_pid}" format+="${delimiter}" format+="#{history_size}" + format+="${delimiter}" + format+="#{window_id}" echo "$format" } @@ -66,6 +68,8 @@ window_format() { format+=":#{window_flags}" format+="${delimiter}" format+="#{window_layout}" + format+="${delimiter}" + format+="#{window_id}" echo "$format" } @@ -79,6 +83,35 @@ state_format() { echo "$format" } +set_canonical_window() { + # We store the canonical window targets using variables named based on the + # window id, because Bash <4.0 doesn't support associative arrays. This + # technique is based on https://stackoverflow.com/a/11776875. + local session_name="$1" + local window_number="$2" + local window_id_num="${3:1}" + local window_target="${session_name}:${window_number}" + + printf -v "CANONICAL_WINDOW__${window_id_num}" %s "$window_target" +} + +get_canonical_window() { + local window_id_num="${1:1}" + + var_name="CANONICAL_WINDOW__${window_id_num}" + echo "${!var_name}" +} + +is_canonical_window() { + local session_name="$1" + local window_number="$2" + local window_id="$3" + local window_target="${session_name}:${window_number}" + + canonical_window=$(get_canonical_window "$window_id") + [[ "$canonical_window" == "$window_target" ]] +} + dump_panes_raw() { tmux list-panes -a -F "$(pane_format)" } @@ -223,15 +256,40 @@ fetch_and_dump_grouped_sessions(){ fi } +set_canonical_windows() { + # When multiple windows are linked via link-window, they will all share the + # same window_id. For each window_id, we arbitrarily pick one of the linked + # window locations (session_name:window_number) to be the canonical window. We + # will only output information for that window, and output a link line for all + # other windows linked to the given id. + + # NB: We use bash process substitution instead of piping into the while + # loop to avoid losing the variables set in set_canonical_window. See + # http://mywiki.wooledge.org/BashFAQ/024 for more info. + while IFS=$d read line_type session_name window_index window_active window_flags window_layout window_id; do + # not saving windows from grouped sessions + if is_session_grouped "$session_name"; then + continue + fi + # We run this for every window, so that for any given window_id, the + # final time we encounter it will be the location we call canonical. + # This is arbitrary, so just picked easiest to implement. + set_canonical_window "$session_name" "$window_index" "$window_id" + done < <(dump_windows_raw) +} + # translates pane pid to process command running inside a pane dump_panes() { local full_command dump_panes_raw | - while IFS=$d read line_type session_name window_number window_name window_active window_flags pane_index dir pane_active pane_command pane_pid history_size; do + while IFS=$d read line_type session_name window_number window_name window_active window_flags pane_index dir pane_active pane_command pane_pid history_size window_id; do # not saving panes from grouped sessions if is_session_grouped "$session_name"; then continue fi + if ! is_canonical_window "$session_name" "$window_number" "$window_id"; then + continue + fi full_command="$(pane_full_command $pane_pid)" dir=$(echo $dir | sed 's/ /\\ /') # escape all spaces in directory path 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}" @@ -240,12 +298,17 @@ dump_panes() { dump_windows() { dump_windows_raw | - while IFS=$d read line_type session_name window_index window_active window_flags window_layout; do + while IFS=$d read line_type session_name window_index window_active window_flags window_layout window_id; do # not saving windows from grouped sessions if is_session_grouped "$session_name"; then continue fi - echo "${line_type}${d}${session_name}${d}${window_index}${d}${window_active}${d}${window_flags}${d}${window_layout}" + if is_canonical_window "$session_name" "$window_index" "$window_id"; then + echo "${line_type}${d}${session_name}${d}${window_index}${d}${window_active}${d}${window_flags}${d}${window_layout}" + else + canonical_window=$(get_canonical_window "$window_id") + echo "link${d}${session_name}${d}${window_index}${d}${canonical_window}" + fi done } @@ -256,7 +319,7 @@ dump_state() { dump_pane_contents() { local pane_contents_area="$(get_tmux_option "$pane_contents_area_option" "$default_pane_contents_area")" dump_panes_raw | - while IFS=$d read line_type session_name window_number window_name window_active window_flags pane_index dir pane_active pane_command pane_pid history_size; do + while IFS=$d read line_type session_name window_number window_name window_active window_flags pane_index dir pane_active pane_command pane_pid history_size window_id; do capture_pane_contents "${session_name}:${window_number}.${pane_index}" "$history_size" "$pane_contents_area" done } @@ -281,6 +344,7 @@ save_all() { local last_resurrect_file="$(last_resurrect_file)" mkdir -p "$(resurrect_dir)" fetch_and_dump_grouped_sessions > "$resurrect_file_path" + set_canonical_windows dump_panes >> "$resurrect_file_path" dump_windows >> "$resurrect_file_path" dump_state >> "$resurrect_file_path"