all repos

tmux-stare @ 4443c84cd1bb7bdfb96953a1b820145f10553e20

session manager, but my session manager
4 files changed, 86 insertions(+), 196 deletions(-)
make easier to delete sessions
Author: Oleksandr Smirnov olexsmir@gmail.com
Committed at: 2026-05-31 11:27:52 +0300
Authored at: 2026-04-14 21:46:29 +0300
Change ID: kpmnnzuvyysxlurwwqymnnmoztzuqqxn
Parent: 29a70cd
D scripts/_session_renamed.sh
···
        1
        
        -#!/usr/bin/env bash

      
        2
        
        -CURRENT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"

      
        3
        
        -source "$CURRENT_DIR/helpers.sh"

      
        4
        
        -source "$CURRENT_DIR/sessions.sh"

      
        5
        
        -

      
        6
        
        -main() {

      
        7
        
        -  if [[ "${1:-}" == "sync" ]]; then

      
        8
        
        -    sync_session_id_map

      
        9
        
        -    return 0

      
        10
        
        -  fi

      
        11
        
        -

      
        12
        
        -  local session_id="$1"

      
        13
        
        -  local new_name="$2"

      
        14
        
        -  handle_session_renamed_hook "$session_id" "$new_name"

      
        15
        
        -}

      
        16
        
        -main "$@"

      
M scripts/pick.sh
···
        73
        73
           local session_file="$save_dir/${session_name}_last"

      
        74
        74
         

      
        75
        75
           local result

      
        76
        
        -  result=$(printf "Cancel\nDelete" | fzf --header="Delete saved session '$session_name'?")

      
        
        76
        +  result=$(printf "Delete\nCancel" | fzf --header="Delete saved session '$session_name'?")

      
        77
        77
           if [[ "$result" == "Delete" ]]; then

      
        78
        78
             rm -f "$session_file"

      
        79
        79
           fi

      
M scripts/sessions.sh
···
        13
        13
           fi

      
        14
        14
         }

      
        15
        15
         

      
        16
        
        -session_id_map_file() { echo "$(get_opt_dir)/.session_ids" }

      
        17
        
        -sync_session_id_map() {

      
        18
        
        -  local map_file="$(session_id_map_file)"

      
        19
        
        -  tmux list-sessions -F "#{session_id}${S}#{session_name}" >"$map_file"

      
        20
        
        -}

      
        21
        
        -

      
        22
        
        -get_session_id_by_name() {

      
        23
        
        -  local session_name="$1"

      
        24
        
        -  tmux display-message -p -t "$session_name" "#{session_id}" 2>/dev/null

      
        25
        
        -}

      
        26
        
        -

      
        27
        
        -get_session_name_by_id() {

      
        28
        
        -  local session_id="$1"

      
        29
        
        -  local map_file="$(session_id_map_file)"

      
        30
        
        -  [[ -f "$map_file" ]] || return 1

      
        31
        
        -  awk -F"$S" -v session_id="$session_id" '$1 == session_id { print $2; exit }' "$map_file"

      
        32
        
        -}

      
        33
        
        -

      
        34
        
        -upsert_session_id_entry() {

      
        35
        
        -  local session_id="$1"

      
        36
        
        -  local session_name="$2"

      
        37
        
        -  local map_file="$(session_id_map_file)"

      
        38
        
        -  local tmp_file="${map_file}.tmp"

      
        39
        
        -

      
        40
        
        -  [[ -z "$session_id" || -z "$session_name" ]] && return 1

      
        41
        
        -  [[ -f "$map_file" ]] || : >"$map_file"

      
        42
        
        -

      
        43
        
        -  awk -F"$S" -v OFS="$S" -v session_id="$session_id" -v session_name="$session_name" '

      
        44
        
        -    $1 == session_id {

      
        45
        
        -      $2 = session_name

      
        46
        
        -      found = 1

      
        47
        
        -    }

      
        48
        
        -    { print }

      
        49
        
        -    END {

      
        50
        
        -      if (!found) print session_id, session_name

      
        51
        
        -    }

      
        52
        
        -  ' "$map_file" >"$tmp_file" && mv "$tmp_file" "$map_file"

      
        53
        
        -}

      
        54
        
        -

      
        55
        
        -remember_session_identity() {

      
        56
        
        -  local session_name="$1"

      
        57
        
        -  local session_id

      
        58
        
        -  session_id="$(get_session_id_by_name "$session_name")"

      
        59
        
        -  [[ -n "$session_id" ]] && upsert_session_id_entry "$session_id" "$session_name"

      
        60
        
        -}

      
        61
        
        -

      
        62
        
        -rename_session_files() {

      
        
        16
        +rename_session() {

      
        63
        17
           local old="$1"

      
        64
        18
           local new="$2"

      
        65
        19
           local dir="$(get_opt_dir)"

      
        66
        
        -  local old_last="${dir}/${old}_last"

      
        67
        
        -  local new_last="${dir}/${new}_last"

      
        68
        
        -  local global_last="${dir}/last"

      
        69
        20
         

      
        70
        21
           [[ -z "$old" || -z "$new" || "$old" == "$new" ]] && return 1

      
        71
        
        -  [[ -e "$old_last" ]] || return 0

      
        72
        
        -  [[ -e "$new_last" ]] && return 1

      
        73
        
        -

      
        74
        
        -  if [[ -L "$old_last" ]]; then

      
        75
        
        -    local actual="$(readlink "$old_last")"

      
        76
        
        -    local actual_name

      
        77
        
        -    local new_actual

      
        78
        
        -    [[ "$actual" != /* ]] && actual="${dir}/${actual}"

      
        79
        
        -

      
        80
        
        -    actual_name="$(basename "$actual")"

      
        81
        
        -    if [[ "$actual_name" == "${old}_"* && -e "$actual" ]]; then

      
        82
        
        -      new_actual="${dir}/${new}_${actual_name#${old}_}"

      
        83
        
        -      mv "$actual" "$new_actual"

      
        84
        
        -      ln -sf "$new_actual" "$new_last"

      
        85
        
        -      rm -f "$old_last"

      
        86
        
        -    else

      
        87
        
        -      mv "$old_last" "$new_last"

      
        88
        
        -    fi

      
        89
        
        -  else

      
        90
        
        -    mv "$old_last" "$new_last"

      
        91
        
        -  fi

      
        92
        
        -

      
        93
        
        -  if [[ -L "$global_last" ]]; then

      
        94
        
        -    local global_target="$(readlink "$global_last")"

      
        95
        
        -    local global_target_name="$(basename "$global_target")"

      
        96
        
        -    if [[ "$global_target_name" == "${old}_last" ]]; then

      
        97
        
        -      ln -sf "$new_last" "$global_last"

      
        98
        
        -    fi

      
        99
        
        -  fi

      
        100
        
        -}

      
        101
        
        -

      
        102
        
        -apply_session_rename() {

      
        103
        
        -  local session_id="$1"

      
        104
        
        -  local new_name="$2"

      
        105
        
        -  local old_name="${3:-}"

      
        106
        
        -

      
        107
        
        -  [[ -z "$session_id" || -z "$new_name" ]] && return 1

      
        108
        
        -  [[ -n "$old_name" ]] || old_name="$(get_session_name_by_id "$session_id" || true)"

      
        109
        
        -

      
        110
        
        -  if [[ -n "$old_name" && "$old_name" != "$new_name" ]]; then

      
        111
        
        -    rename_session_files "$old_name" "$new_name"

      
        112
        
        -  fi

      
        113
        
        -

      
        114
        
        -  upsert_session_id_entry "$session_id" "$new_name"

      
        115
        
        -}

      
        116
        
        -

      
        117
        
        -rename_session() {

      
        118
        
        -  local old="$1"

      
        119
        
        -  local new="$2"

      
        120
        
        -  local session_id

      
        121
        
        -

      
        122
        
        -  [[ -z "$new" || "$old" == "$new" ]] && return 1

      
        123
        22
           [[ -e "$(get_opt_dir)/${new}_last" ]] && return 1

      
        124
        23
         

      
        125
        24
           tmux has-session -t "$new" 2>/dev/null && return 1

      
        126
        
        -  session_id="$(get_session_id_by_name "$old")"

      
        127
        
        -  [[ -n "$session_id" ]] || return 1

      
        128
        
        -  tmux rename-session -t "$old" "$new" 2>/dev/null || return 1

      
        129
        
        -  apply_session_rename "$session_id" "$new" "$old"

      
        130
        
        -}

      
        
        25
        +  tmux rename-session -t "$old" "$new" 2>/dev/null

      
        131
        26
         

      
        132
        
        -handle_session_renamed_hook() {

      
        133
        
        -  local session_id="$1"

      
        134
        
        -  local new_name="$2"

      
        135
        
        -

      
        136
        
        -  [[ -z "$session_id" || -z "$new_name" ]] && return 1

      
        137
        
        -  apply_session_rename "$session_id" "$new_name"

      
        
        27
        +  local old_last="${dir}/${old}_last"

      
        
        28
        +  [[ -L "$old_last" ]] && {

      
        
        29
        +    local actual="$(readlink "$old_last")"

      
        
        30
        +    local new_actual="${dir}/${new}_$(basename "$actual" | cut -d_ -f2-)"

      
        
        31
        +    mv "$actual" "$new_actual"

      
        
        32
        +    ln -sf "$new_actual" "${dir}/${new}_last"

      
        
        33
        +    rm "$old_last"

      
        
        34
        +  }

      
        138
        35
         }

      
        139
        36
         

      
        140
        37
         # === save

      ···
        152
        49
         

      
        153
        50
         }

      
        154
        51
         

      
        155
        
        -get_pane_child_pids() {

      
        156
        
        -  local pane_pid="$1"

      
        157
        
        -  ps -ao ppid=,pid= | awk -v pane_pid="$pane_pid" '$1 == pane_pid { print $2 }'

      
        158
        
        -}

      
        159
        
        -

      
        160
        52
         format_process_command() {

      
        161
        53
           local pid="$1"

      
        162
        54
           [[ -r "/proc/${pid}/cmdline" ]] || return 1

      
        163
        55
           xargs -0 bash -c 'printf "%q " "$0" "$@"' <"/proc/${pid}/cmdline" 2>/dev/null | sed 's/[[:space:]]*$//'

      
        164
        56
         }

      
        165
        57
         

      
        166
        
        -build_command_from_pids() {

      
        167
        
        -  local pids="$1"

      
        168
        
        -  local pid

      
        169
        
        -  local formatted

      
        170
        
        -  local command=""

      
        171
        
        -  for pid in $pids; do

      
        172
        
        -    formatted="$(format_process_command "$pid")"

      
        173
        
        -    [[ -z "$formatted" ]] && continue

      
        174
        
        -    [[ -n "$command" ]] && command+=" | "

      
        175
        
        -    command+="$formatted"

      
        
        58
        +get_descendant_pids() {

      
        
        59
        +  local root_pid="$1"

      
        
        60
        +  local proc_table

      
        
        61
        +  local -A seen

      
        
        62
        +  local frontier

      
        
        63
        +

      
        
        64
        +  [[ -n "$root_pid" ]] || return 1

      
        
        65
        +  proc_table="$(ps -ao ppid=,pid= --sort=pid)"

      
        
        66
        +  frontier="$root_pid"

      
        
        67
        +  seen["$root_pid"]="1"

      
        
        68
        +

      
        
        69
        +  while [[ -n "$frontier" ]]; do

      
        
        70
        +    local next_frontier=""

      
        
        71
        +    local ppid

      
        
        72
        +    local pid

      
        
        73
        +

      
        
        74
        +    while read -r ppid pid; do

      
        
        75
        +      local parent

      
        
        76
        +      for parent in $frontier; do

      
        
        77
        +        [[ "$ppid" == "$parent" ]] || continue

      
        
        78
        +        [[ -n "${seen[$pid]:-}" ]] && continue

      
        
        79
        +        seen["$pid"]="1"

      
        
        80
        +        printf "%s\n" "$pid"

      
        
        81
        +        next_frontier+=" $pid"

      
        
        82
        +      done

      
        
        83
        +    done <<<"$proc_table"

      
        
        84
        +

      
        
        85
        +    frontier="${next_frontier# }"

      
        176
        86
           done

      
        177
        
        -  printf "%s" "$command"

      
        
        87
        +}

      
        
        88
        +

      
        
        89
        +get_process_name() {

      
        
        90
        +  local pid="$1"

      
        
        91
        +  local first_arg

      
        
        92
        +  [[ -r "/proc/${pid}/cmdline" ]] || return 1

      
        
        93
        +  first_arg="$(tr '\0' '\n' <"/proc/${pid}/cmdline" 2>/dev/null | head -n1)"

      
        
        94
        +  [[ -n "$first_arg" ]] || return 1

      
        
        95
        +  basename "$first_arg"

      
        178
        96
         }

      
        179
        97
         

      
        180
        
        -collect_process_names() {

      
        181
        
        -  local pids="$1"

      
        
        98
        +select_active_command_pid() {

      
        
        99
        +  local pane_pid="$1"

      
        
        100
        +  local pane_target="$2"

      
        
        101
        +  local pane_current_command

      
        
        102
        +  local pids

      
        182
        103
           local pid

      
        183
        
        -  local first_arg

      
        184
        104
           local name

      
        
        105
        +  local matching_pid=""

      
        
        106
        +  local fallback_pid=""

      
        
        107
        +  local descendant_count=0

      
        
        108
        +

      
        
        109
        +  pane_current_command="$(tmux display-message -p -t "$pane_target" "#{pane_current_command}" 2>/dev/null)"

      
        
        110
        +  pids="$(get_descendant_pids "$pane_pid")"

      
        
        111
        +

      
        185
        112
           for pid in $pids; do

      
        186
        
        -    [[ -r "/proc/${pid}/cmdline" ]] || continue

      
        187
        
        -    first_arg="$(tr '\0' '\n' <"/proc/${pid}/cmdline" 2>/dev/null | head -n1)"

      
        188
        
        -    [[ -z "$first_arg" ]] && continue

      
        189
        
        -    name="$(basename "$first_arg")"

      
        190
        
        -    [[ -n "$name" ]] && printf "%s\n" "$name"

      
        191
        
        -  done | sort -u

      
        192
        
        -}

      
        
        113
        +    descendant_count=$((descendant_count + 1))

      
        
        114
        +    fallback_pid="$pid"

      
        
        115
        +    [[ -n "$pane_current_command" ]] || continue

      
        
        116
        +    name="$(get_process_name "$pid")" || continue

      
        
        117
        +    [[ "$name" == "$pane_current_command" ]] && matching_pid="$pid"

      
        
        118
        +  done

      
        193
        119
         

      
        194
        
        -strip_prompt_prefix() {

      
        195
        
        -  local line="$1"

      
        196
        
        -  if [[ "$line" =~ [\$\#\%\>][[:space:]]+.+$ ]]; then

      
        197
        
        -    sed -E 's/^.*[#$%>][[:space:]]+//' <<<"$line"

      
        
        120
        +  if [[ -n "$matching_pid" ]]; then

      
        
        121
        +    printf "%s" "$matching_pid"

      
        
        122
        +  elif [[ "$descendant_count" == "1" ]]; then

      
        
        123
        +    printf "%s" "$fallback_pid"

      
        198
        124
           fi

      
        199
        125
         }

      
        200
        126
         

      
        201
        
        -find_command_from_pane_history() {

      
        202
        
        -  local pane_target="$1"

      
        203
        
        -  local process_names="$2"

      
        204
        
        -  local line

      
        205
        
        -  local candidate

      
        206
        
        -  local name

      
        207
        
        -  while IFS= read -r line; do

      
        208
        
        -    [[ -z "$line" ]] && continue

      
        209
        
        -    candidate="$(strip_prompt_prefix "$line")"

      
        210
        
        -    [[ -z "$candidate" ]] && continue

      
        211
        
        -    while IFS= read -r name; do

      
        212
        
        -      [[ -z "$name" ]] && continue

      
        213
        
        -      if [[ "$candidate" == *"$name"* ]]; then

      
        214
        
        -        printf "%s" "$candidate"

      
        215
        
        -        return 0

      
        216
        
        -      fi

      
        217
        
        -    done <<<"$process_names"

      
        218
        
        -  done < <(tmux capture-pane -pJ -S -200 -t "$pane_target" 2>/dev/null | tac)

      
        
        127
        +capture_running_command() {

      
        
        128
        +  local pane_pid="$1"

      
        
        129
        +  local pane_target="$2"

      
        
        130
        +  local command_pid

      
        
        131
        +

      
        
        132
        +  command_pid="$(select_active_command_pid "$pane_pid" "$pane_target")"

      
        
        133
        +  [[ -n "$command_pid" ]] || return 1

      
        
        134
        +  format_process_command "$command_pid"

      
        219
        135
         }

      
        220
        136
         

      
        221
        137
         save_panes() {

      ···
        224
        140
           local format="pane$S#{pane_index}$S#{pane_current_path}$S#{pane_active}$S#{window_index}$S#{pane_pid}"

      
        225
        141
           tmux list-panes -s -t "$session_name" -F "$format" |

      
        226
        142
             while IFS="$S" read -r line; do

      
        
        143
        +      local pane_target

      
        
        144
        +      local command

      
        227
        145
               IFS="$S" read -r _ pane_index _ _ window_index pane_pid <<<"$line"

      
        228
        
        -      pids="$(get_pane_child_pids "$pane_pid")"

      
        229
        
        -      command="$(build_command_from_pids "$pids")"

      
        230
        
        -

      
        231
        
        -      if [[ -n "$command" ]]; then

      
        232
        
        -        process_names="$(collect_process_names "$pids")"

      
        233
        
        -        pane_target="${session_name}:${window_index}.${pane_index}"

      
        234
        
        -        pane_command="$(find_command_from_pane_history "$pane_target" "$process_names")"

      
        235
        
        -        [[ -n "$pane_command" ]] && command="$pane_command"

      
        236
        
        -      fi

      
        
        146
        +      pane_target="${session_name}:${window_index}.${pane_index}"

      
        
        147
        +      command="$(capture_running_command "$pane_pid" "$pane_target")"

      
        237
        148
         

      
        238
        149
               awk -v command="$command" \

      
        239
        150
                 'BEGIN {FS=OFS="\t"} {$6=command; print}' \

      ···
        268
        179
           save_panes "$session_name" "$save_file"

      
        269
        180
           link_session_last "$save_file" "$last_file"

      
        270
        181
           link_last "$last_file" "$save_dir"

      
        271
        
        -  remember_session_identity "$session_name"

      
        272
        182
         }

      
        273
        183
         

      
        274
        184
         save_all_sessions() {

      
M stare.tmux
···
        15
        15
         

      
        16
        16
         main() {

      
        17
        17
           local pick_key="$(get_opt_pick)"

      
        18
        
        -  [[ -n "$pick_key" ]] && tmux bind-key "$pick_key" run-shell "bash \"$CURRENT_DIR/scripts/_pick.sh\""

      
        
        18
        +  [[ -n "$pick_key" ]] && tmux bind-key "$pick_key" run-shell "$CURRENT_DIR/scripts/_pick.sh"

      
        19
        19
         

      
        20
        20
           local save_key="$(get_opt_save)"

      
        21
        
        -  [[ -n "$save_key" ]] && tmux bind-key "$save_key" run-shell "bash \"$CURRENT_DIR/scripts/_save.sh\""

      
        
        21
        +  [[ -n "$save_key" ]] && tmux bind-key "$save_key" run-shell "$CURRENT_DIR/scripts/_save.sh"

      
        22
        22
         

      
        23
        23
           local start_action="$(get_opt_start)"

      
        24
        24
           if [[ "$(get_opt_initialized)" == "0" ]]; then

      
        25
        25
             if [[ "$start_action" == "last" ]]; then

      
        26
        
        -      tmux run-shell -b "sleep 0.1 && bash \"$CURRENT_DIR/scripts/_restore.sh\""

      
        
        26
        +      tmux run-shell -b "sleep 0.1 && '$CURRENT_DIR/scripts/_restore.sh'"

      
        27
        27
             elif [[ "$start_action" == "pick" ]]; then

      
        28
        
        -      tmux run-shell -b "sleep 0.1 && bash \"$CURRENT_DIR/scripts/_pick.sh\""

      
        
        28
        +      tmux run-shell -b "sleep 0.1 && '$CURRENT_DIR/scripts/_pick.sh'"

      
        29
        29
             fi

      
        30
        30
         

      
        31
        31
             set_opt_initialized "1"

      
        32
        32
           fi

      
        33
        
        -

      
        34
        
        -  # rename hook

      
        35
        
        -  tmux set-hook -g session-renamed "run-shell \"bash '$CURRENT_DIR/scripts/_session_renamed.sh' #{q:hook_session} #{q:hook_session_name}\""

      
        36
        
        -  tmux run-shell "bash '$CURRENT_DIR/scripts/_session_renamed.sh' sync"

      
        37
        33
         

      
        38
        34
           add_save_interpolation

      
        39
        35
         }