diff --git a/.editorconfig b/.editorconfig new file mode 100644 index 0000000..cd69a14 --- /dev/null +++ b/.editorconfig @@ -0,0 +1,7 @@ +[*.tmux] +indent_style = space +indent_size = 4 + +[*.sh] +indent_style = space +indent_size = 2 diff --git a/session-wizard.sh b/session-wizard.sh index 718886c..721a000 100755 --- a/session-wizard.sh +++ b/session-wizard.sh @@ -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 @@ -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 - diff --git a/session-wizard.tmux b/session-wizard.tmux index dd358b5..ed6e72a 100755 --- a/session-wizard.tmux +++ b/session-wizard.tmux @@ -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" @@ -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() { diff --git a/src/duplicate-session.sh b/src/duplicate-session.sh new file mode 100755 index 0000000..531394f --- /dev/null +++ b/src/duplicate-session.sh @@ -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 "$@" diff --git a/src/helpers.sh b/src/helpers.sh new file mode 100644 index 0000000..002d7fd --- /dev/null +++ b/src/helpers.sh @@ -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;" +} diff --git a/tests/helpers.bats b/tests/helpers.bats new file mode 100644 index 0000000..b484f15 --- /dev/null +++ b/tests/helpers.bats @@ -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" +} diff --git a/tests/session.bats b/tests/session.bats new file mode 100644 index 0000000..d401210 --- /dev/null +++ b/tests/session.bats @@ -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 <