Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Feature: Duplicate session with automatically grouping #10

Draft
wants to merge 3 commits into
base: main
Choose a base branch
from
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
7 changes: 7 additions & 0 deletions .editorconfig
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
[*.tmux]
indent_style = space
indent_size = 4

[*.sh]
indent_style = space
indent_size = 2
5 changes: 2 additions & 3 deletions session-wizard.sh
Original file line number Diff line number Diff line change
Expand Up @@ -16,7 +16,7 @@ if [ "$1" ]; then
else
# No argument is given. Use FZF
RESULT=$((tmux list-sessions -F "#{session_last_attached} #{session_name}: #{session_windows} window(s)\
#{?session_grouped, (group ,}#{session_group}#{?session_grouped,),}#{?session_attached, (attached),}"\
#{?session_grouped, (group ,}#{session_group}#{?session_grouped,),}#{?session_attached, (attached),}"\
| sort -r | (if [ -n "$TMUX" ]; then grep -v " $(tmux display-message -p '#S'):"; else cat; fi) | cut -d' ' -f2-; zoxide query -l) | $(__fzfcmd) --reverse)
if [ -z "$RESULT" ]; then
exit 0
Expand All @@ -40,6 +40,5 @@ fi
if [ -z "$TMUX" ]; then
tmux attach -t $SESSION
else
tmux switch-client -t $SESSION
tmux switch-client -t "$SESSION"
fi

9 changes: 7 additions & 2 deletions session-wizard.tmux
Original file line number Diff line number Diff line change
@@ -1,9 +1,9 @@
#!/usr/bin/env bash

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

default_key_bindings_session_wizard="T"
tmux_option_session_wizard="@session-wizard"
default_key_bindings_session_wizard="T"
tmux_option_session_wizard_height="@session-wizard-height"
default_height=40
tmux_option_session_wizard_width="@session-wizard-width"
Expand All @@ -21,6 +21,11 @@ set_session_wizard_options() {
for key in $(echo "${key_bindings}" | sed 's/ /\n/g'); do
tmux bind "$key" display-popup -w "$width"% -h "$height"% -E "$CURRENT_DIR/session-wizard.sh"
done

# TODO: Is it worth to have a variable for this? For now it's used without a variables.
local duplicate_key_bindings
duplicate_key_bindings=$(get_tmux_option "@session-wizard-duplicate" "C-t")
tmux bind "$duplicate_key_bindings" display-popup -w "$width"% -h "$height"% -E "$CURRENT_DIR/src/duplicate-session.sh --switch"
}

get_tmux_option() {
Expand Down
49 changes: 49 additions & 0 deletions src/duplicate-session.sh
Original file line number Diff line number Diff line change
@@ -0,0 +1,49 @@
#
# Duplicate a session and crete group with it
#
# Usage: duplicate-session [-g|--group-with-last-active] [-s|--switch]
# -a, --group-with-last-active: Group with last active session, work also if you
# are not in Tmux (default: false)
# -s, --switch: Switch to the new created session (default: false)
#
CURRENT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
source "$CURRENT_DIR/helpers.sh"

main() {
local session new_session

local group_with_last_active=false
local switch=false
while [ "$#" -gt 0 ]; do
case "$1" in
-a | --group-with-last-active)
shift
group_with_last_active=true
;;
-s | --switch)
shift
switch=true
;;
*)
echo "Unknown argument: $1"
return 1
;;
esac
done

if [ "$group_with_last_active" = "true" ]; then
session=$(tmux display-message -p '#S')
else
# shellcheck disable=SC2119
session=$(session_list | show_fzf | cut -d':' -f1)
fi

local session_folder
session_folder=$(tmux display-message -p -t "$session" "#{session_path}")
new_session=$(tmux new-session -P -d -c "$session_folder" -t "$session")
if [ "$switch" = "true" ]; then
switch_client "$new_session"
fi
}

main "$@"
72 changes: 72 additions & 0 deletions src/helpers.sh
Original file line number Diff line number Diff line change
@@ -0,0 +1,72 @@
_normalize() {
# Replace dots with underline, replace all "special" chars with dashes and convert everything to lowercase.
cat | tr '_' - | tr ' ' - | tr ':' - | tr . _ | tr '[:upper:]' '[:lower:]'
}

# helper functions
get_tmux_option() {
local option="$1"
local default_value="$2"
local option_value
option_value=$(tmux show-option -gqv "$option")
if [ -z "$option_value" ]; then
echo "$default_value"
else
echo "$option_value"
fi
}

# Switch to the new created session
switch_client() {
if [ -z "$TMUX" ]; then
tmux attach -t "$*"
else
tmux switch-client -t "$*"
fi
}

# Get the list of sessions
# The output is in the format:
# last_attached session_name: windows_count (group group_name) (attached)
session_list() {
local list
list=$(tmux list-sessions -F "#{session_last_attached} #{session_name}: #{session_windows} window(s) #{?session_grouped, (group ,}#{session_group}#{?session_grouped,),}#{?session_attached, (attached),}")
if [ -n "$TMUX" ] && [ "$1" = "--without-active" ]; then
list=$(echo "$list" | grep -v " $(tmux display-message -p '#S'):")
fi
echo "$list" | sort -r | cut -d' ' -f2-
}

# Show the fzf window, data is passed through stdin
show_fzf() {
local fzf_cmd
if [ -n "$TMUX_PANE" ] && { [ "${FZF_TMUX:-0}" != 0 ] || [ -n "$FZF_TMUX_OPTS" ]; }; then
fzf_cmd="fzf-tmux ${FZF_TMUX_OPTS:--d${FZF_TMUX_HEIGHT:-40%}} -- "
else
fzf_cmd="fzf"
fi
cat | $fzf_cmd --reverse
}

session_name() {
if [ "$1" = "--folder" ]; then
shift
basename "$@" | _normalize
elif [ "$1" = "--full-path" ]; then
shift
echo "$@" | tr '/' '\n' | _normalize | tr '\n' '/' | sed 's/\/$//'
elif [ "$1" = "--short-path" ]; then
shift
echo "$(echo "${@%/*}" | sed -r 's;/([^/]{,2})[^/]*;/\1;g' | _normalize)/$(basename "$@" | _normalize)"
else
echo "Wrong argument, you can use --last-normalized, --full or --shortened, got $1"
return 1
fi
}

fold_home() {
local symbol="$1"
shift
# shellcheck disable=SC2001
echo "$@" | sed "s;^$HOME;$symbol;"
}
51 changes: 51 additions & 0 deletions tests/helpers.bats
Original file line number Diff line number Diff line change
@@ -0,0 +1,51 @@
setup() {
bats_load_library 'bats-support'
bats_load_library 'bats-assert'
DIR="$(cd "$(dirname "$BATS_TEST_FILENAME")" >/dev/null 2>&1 && pwd)"
SRC_DIR="$DIR/../src"
source "$SRC_DIR/helpers.sh"
TEST_PATH="/MOO/.foo BAR/.moo FOO-bar.baz"
}

unset() {
unset TEST_PATH
}

# TODO: use better stubbing for tmux (tmux show-option)
@test "get tmux option with default value" {
# stub tmux
function tmux() {
assert_equal "$1" "show-option"
assert_equal "$3" "moo-foo-bar"
}
run get_tmux_option "moo-foo-bar" "bar"
assert_output "bar"
}

@test "get tmux option with value" {
# stub tmux
function tmux() {
assert_equal "$1" "show-option"
assert_equal "$3" "moo-foo-bar"
# option value is set to "foo"
echo "foo"
}
run get_tmux_option "moo-foo-bar" "bar"
assert_output "foo"
}

@test "fold home directory to symbol" {
HOME="/home/user"
local folder="/home/user"

run fold_home "++" "$folder"
assert_output "++"
}

@test "fold home directory to symbol with path" {
local HOME="/home/user"
local folder="/home/user/.foo/M O O"

run fold_home "++" "$folder"
assert_output "++/.foo/M O O"
}
58 changes: 58 additions & 0 deletions tests/session.bats
Original file line number Diff line number Diff line change
@@ -0,0 +1,58 @@
setup() {
bats_load_library 'bats-support'
bats_load_library 'bats-assert'
DIR="$(cd "$(dirname "$BATS_TEST_FILENAME")" >/dev/null 2>&1 && pwd)"
SRC_DIR="$DIR/../src"
source "$SRC_DIR/helpers.sh"
TEST_PATH="/MOO/.foo BAR/.moo FOO-bar.baz"
}

# TODO: Is this the best way to mock tmux?
tmux() {
if [ "$1" = "list-sessions" ]; then
# Session list, first collumn is attached timestamp
cat <<HERE
15 /infructure: 1 window(s)
20 /tmux-session-wizard: 3 window(s) (group /tmux-session-wizard) (attached)
30 /tmux-session-wizard-3: 3 window(s) (group /tmux-session-wizard) (attached)
35 /joe doe/project: 1 window(s)
40 /tmux-session-wizard-5: 3 window(s) (group /tmux-session-wizard)
HERE
elif [ "$1" = "display-message" ]; then
# Current session
echo "/joe doe/project"
fi
}

# Session list tests
@test "session list should return a list of sessions in reverse attached order" {
run session_list
assert_line -n 0 -e '^/tmux-session-wizard-5:'
assert_line -n 1 -e '^/joe doe/project:'
assert_line -n 2 -e '^/tmux-session-wizard-3:'
assert_line -n 3 -e '^/tmux-session-wizard:'
assert_line -n 4 -e '^/infructure:'
}

@test "session list should return list without current session" {
run session_list --without-active

refute_output --partial "/joe doe/project"
assert_line -n 0 -e '^/tmux-session-wizard-5:'
}

# Session name tests
@test "create session name with last folder in path" {
run session_name --folder "$TEST_PATH"
assert_output "_moo-foo-bar_baz"
}

@test "create session name with full path" {
run session_name --full-path "$TEST_PATH"
assert_output "/moo/_foo-bar/_moo-foo-bar_baz"
}

@test "create session name with shortened path and last folder in path" {
run session_name --short-path "$TEST_PATH"
assert_output "/mo/_f/_moo-foo-bar_baz"
}