X-Git-Url: https://git.madduck.net/etc/tmux.git/blobdiff_plain/93b15f29293e906ec9799542b6c5070550d94120..87174fb5e41daba4e9e39ab6ffc1d3d2cbfe301a:/.bin/xpanes?ds=sidebyside diff --git a/.bin/xpanes b/.bin/xpanes index 5b3d1f9..27c139f 100755 --- a/.bin/xpanes +++ b/.bin/xpanes @@ -5,17 +5,14 @@ readonly XP_SHELL="/usr/bin/env bash" # @Filename xpanes set -u -readonly XP_VERSION="4.1.1" +readonly XP_VERSION="4.1.3" ## trap might be updated in 'xpns_pre_execution' function -trap 'rm -f "${XP_CACHE_HOME}"/__xpns_*$$; rm -f "${XP_DEFAULT_SOCKET_PATH}"' EXIT +trap 'rm -f "${XP_CACHE_HOME}"/__xpns_*$$; xpns_clean_session' EXIT ## -------------------------------- # Error constants ## -------------------------------- -# Undefined or General errors -readonly XP_EUNDEF=1 - # Invalid option/argument readonly XP_EINVAL=4 @@ -49,7 +46,7 @@ readonly XP_ENOCMD=127 # XP_THIS_FILE_NAME is supposed to be "xpanes". readonly XP_THIS_FILE_NAME="${0##*/}" -readonly XP_THIS_DIR="$(cd "$(dirname "${BASH_SOURCE[0]:-${(%):-%N}}")" && pwd)" +readonly XP_THIS_DIR="$(cd "$(dirname "${BASH_SOURCE[0]:-$0}")" && pwd)" readonly XP_ABS_THIS_FILE_NAME="${XP_THIS_DIR}/${XP_THIS_FILE_NAME}" # Prevent cache directory being created under root / directory in any case. @@ -90,13 +87,14 @@ TMUX_XPANES_LOG_DIRECTORY="${TMUX_XPANES_LOG_DIRECTORY:-${XP_DEFAULT_TMUX_XPANES # Initialize Options ## -------------------------------- # options which work individually. -readonly XP_FLAG_OPTIONS="[hVdetxs]" +# readonly XP_FLAG_OPTIONS="[hVdetxs]" # options which need arguments. readonly XP_ARG_OPTIONS="[ISclnCRB]" readonly XP_DEFAULT_LAYOUT="tiled" readonly XP_DEFAULT_REPSTR="{}" readonly XP_DEFAULT_CMD_UTILITY="echo {} " readonly XP_SSH_CMD_UTILITY="ssh -o StrictHostKeyChecking=no {} " +readonly XP_OFS="${XP_OFS:- }" XP_OPTIONS=() XP_ARGS=() XP_STDIN=() @@ -158,7 +156,7 @@ xpns_msg_warning() { } xpns_msg_debug() { - if [[ $XP_OPT_DEBUG -eq 1 ]];then + if [[ $XP_OPT_DEBUG -eq 1 ]]; then xpns_msg "Debug" "$(date "+[%F_%T]"):${FUNCNAME[1]}:$1" fi } @@ -180,7 +178,7 @@ _EOS_ } xpns_usage() { - cat < /dev/null' # In tmux 1.6, 'tmux set-window-option' might be stopped in case of redirection. -xpns_restore_allow_rename () { +xpns_restore_allow_rename() { local _default_allow_rename="$1" local _session="${2-}" if [[ "${_default_allow_rename-}" == "on" ]]; then xpns_msg_debug "Restore original value of 'allow-rename' option." - if [[ -z "${_session-}" ]];then + if [[ -z "${_session-}" ]]; then ${TMUX_XPANES_EXEC} set-window-option -g allow-rename on else ${TMUX_XPANES_EXEC} -S "${_session}" set-window-option -g allow-rename on @@ -325,33 +332,35 @@ xpns_restore_allow_rename () { # func "11" "2" # => 6 # 11 / 2 = 5.5 => ceiling => 6 -xpns_ceiling () { - local _divide="$1";shift +xpns_ceiling() { + local _divide="$1" + shift local _by="$1" - printf "%s\\n" $(( ( _divide + _by - 1 ) / _by )) + printf "%s\\n" $(((_divide + _by - 1) / _by)) } # func "10" "3" # => 4 3 3 # Divide 10 into 3 parts as equally as possible. -xpns_divide_equally () { - local _number="$1";shift +xpns_divide_equally() { + local _number="$1" + shift local _count="$1" local _upper _lower _upper_count _lower_count _upper="$(xpns_ceiling "$_number" "$_count")" - _lower=$(( _upper - 1 )) - _lower_count=$(( _upper * _count - _number )) - _upper_count=$(( _count - _lower_count )) + _lower=$((_upper - 1)) + _lower_count=$((_upper * _count - _number)) + _upper_count=$((_count - _lower_count)) eval "printf '${_upper} %.0s' {1..$_upper_count}" - (( _lower_count > 0 )) && eval "printf '${_lower} %.0s' {1..$_lower_count}" + ((_lower_count > 0)) && eval "printf '${_lower} %.0s' {1..$_lower_count}" } # echo 3 3 3 3 | func # => 3 6 9 12 -xpns_nums_accumulate_sum () { +xpns_nums_accumulate_sum() { local s=0 while read -r n; do - ((s = s + n )) + ((s = s + n)) printf "%s " "$s" done < <( cat | tr ' ' '\n') } @@ -371,12 +380,12 @@ xpns_nums_accumulate_sum () { # 1 [] [] [] [] => 4 rows # 2 [] [] [] [] => 4 rows # 3 [] => 1 rows -xpns_nums_transpose () { +xpns_nums_transpose() { local _colnum="$1" local _spaces= local _result= xpns_msg_debug "column num = $_colnum, input = $*" - _spaces="$(for i in "$@";do + _spaces="$(for i in "$@"; do printf "%${i}s\\n" done)" @@ -386,7 +395,7 @@ xpns_nums_transpose () { ## $ echo 1234 | cut -c 5 ## => result is supposed to be empty printf "%s\\n" "$_spaces" | cut -c "$i" | grep -c ' ' - done < <(xpns_seq 1 "${_colnum}") | xargs)" + done < <(xpns_seq 1 "${_colnum}") | xpns_newline2space)" xpns_msg_debug "result = $_result" printf "%s\\n" "$_result" } @@ -399,33 +408,36 @@ xpns_nums_transpose () { # func "6" 0 20 # => returns 6 4 xpns_adjust_col_row() { - local col="${1:-0}" ;shift - local row="${1:-0}" ;shift - local N="$1" ;shift + local col="${1:-0}" + shift + local row="${1:-0}" + shift + local N="$1" + shift local fix_col_flg local fix_row_flg - (( col != 0 )) && fix_col_flg=1 || fix_col_flg=0 - (( row != 0 )) && fix_row_flg=1 || fix_row_flg=0 + ((col != 0)) && fix_col_flg=1 || fix_col_flg=0 + ((row != 0)) && fix_row_flg=1 || fix_row_flg=0 # This is just a author (@greymd)'s preference. - if (( fix_col_flg == 0 )) && (( fix_row_flg == 0 )) && (( N == 2)) ;then + if ((fix_col_flg == 0)) && ((fix_row_flg == 0)) && ((N == 2)); then col=2 row=1 printf "%d %d\\n" "${col}" "${row}" return fi - # If both valures are provided, col is used. - if (( fix_col_flg == 1 )) && (( fix_row_flg == 1 ));then + # If both values are provided, col is used. + if ((fix_col_flg == 1)) && ((fix_row_flg == 1)); then row=0 fix_row_flg=0 fi # This algorhythm is almost same as tmux default # https://github.com/tmux/tmux/blob/2.8/layout-set.c#L436 - while (( col * row < N )) ;do - (( fix_row_flg != 1 )) && (( row = row + 1 )) - if (( col * row < N ));then - (( fix_col_flg != 1 )) && (( col = col + 1 )) + while ((col * row < N)); do + ((fix_row_flg != 1)) && ((row = row + 1)) + if ((col * row < N)); then + ((fix_col_flg != 1)) && ((col = col + 1)) fi done printf "%d %d\\n" "${col}" "${row}" @@ -441,7 +453,7 @@ xpns_adjust_col_row() { # ccc-3 # # Eval is used because associative array is not supported before bash 4.2 -xpns_unique_line () { +xpns_unique_line() { local _val_name while read -r line; do _val_name="__xpns_hash_$(printf "%s" "${line}" | xpns_value2key)" @@ -468,19 +480,17 @@ xpns_unique_line () { # ccc-2_1234_20160101.log # ccc-3_1234_20160101.log # -xpns_log_filenames () { +xpns_log_filenames() { local _arg_fmt="$1" local _full_fmt= _full_fmt="$(date "+${_arg_fmt}")" - cat \ - | \ + cat | # 1st argument + '-' + unique number (avoid same argument has same name) - xpns_unique_line \ - | while read -r _arg - do - cat <<<"${_full_fmt}" \ - | sed "s/\\[:ARG:\\]/${_arg}/g" \ - | sed "s/\\[:PID:\\]/$$/g" + xpns_unique_line | + while read -r _arg; do + cat <<< "${_full_fmt}" | + sed "s/\\[:ARG:\\]/${_arg}/g" | + sed "s/\\[:PID:\\]/$$/g" done } @@ -547,18 +557,15 @@ xpns_arr2args() { if [[ $# -lt 1 ]]; then return 0 fi - for i in "$@" ;do + for i in "$@"; do _arg="${i}" # Use 'cat <<<"input"' command instead of 'echo', # because such the command recognizes option like '-e'. - cat <<<"${_arg}" \ - | \ + cat <<< "${_arg}" | # Escaping single quotations. - sed "s/'/'\"'\"'/g" \ - | \ + sed "s/'/'\"'\"'/g" | # Surround argument with single quotations. - sed "s/^/'/;s/$/' /" \ - | \ + sed "s/^/'/;s/$/' /" | # Remove new lines tr -d '\n' done @@ -568,14 +575,16 @@ xpns_arr2args() { # ex, $2 = 'aaa bbb ccc' # return = aaa-12345(PID) xpns_generate_window_name() { - local _unprintable_str="${1-}"; shift + local _unprintable_str="${1-}" + shift # Leave first 200 characters to prevent # the name exceed the maximum length of tmux window name (2000 byte). - printf "%s\\n" "${1:-${_unprintable_str}}" \ - | ( read -r _name _ && printf "%s\\n" "${_name:0:200}-$$" ) + printf "%s\\n" "${1:-${_unprintable_str}}" | + ( read -r _name _ && printf "%s\\n" "${_name:0:200}-$$" ) } -# Convert string to another string which can be handled as tmux window name. +# Convert any string (including multi-byte chars) to another string +# which can be handled as tmux window name. xpns_value2key() { od -v -tx1 -An | tr -dc 'a-zA-Z0-9' | tr -d '\n' } @@ -590,7 +599,10 @@ xpns_key2value() { # Remove empty lines # This function behaves like `awk NF` xpns_rm_empty_line() { - { cat; printf "\\n";} | while IFS= read -r line;do + { + cat + printf "\\n" + } | while IFS= read -r line; do # shellcheck disable=SC2086 set -- ${line-} if [[ $# != 0 ]]; then @@ -599,47 +611,41 @@ xpns_rm_empty_line() { done } -# Extract matched patterns from string -# $ xpns_extract_matched "aaa123bbb" "[0-9]{3}" -# => "123" -xpns_extract_matched() { - local _args="$1" ;shift - local _regex="($1)" - if [[ $_args =~ $_regex ]];then - printf "%s" "${BASH_REMATCH[0]}" - fi -} - # Enable logging feature to the all the panes in the target window. xpns_enable_logging() { - local _window_name="$1" ; shift - local _index_offset="$1" ; shift - local _log_dir="$1" ; shift - local _log_format="$1" ; shift - local _unprintable_str="$1" ; shift + local _window_name="$1" + shift + local _index_offset="$1" + shift + local _log_dir="$1" + shift + local _log_format="$1" + shift + local _unprintable_str="$1" + shift local _args=("$@") local _args_num=$(($# - 1)) # Generate log files from arguments. local _idx=0 - while read -r _logfile ; do + while read -r _logfile; do # Start logging xpns_msg_debug "Start logging pipe-pane(cat >> '${_log_dir}/${_logfile}')" ${TMUX_XPANES_EXEC} \ - pipe-pane -t "${_window_name}.$(( _idx + _index_offset ))" \ + pipe-pane -t "${_window_name}.$((_idx + _index_offset))" \ "cat >> '${_log_dir}/${_logfile}'" # Tilde expansion does not work here. - _idx=$(( _idx + 1 )) + _idx=$((_idx + 1)) done < <( - for i in $(xpns_seq 0 "${_args_num}") - do - # Replace empty string. - printf "%s\\n" "${_args[i]:-${_unprintable_str}}" - done | xpns_log_filenames "${_log_format}" + for i in $(xpns_seq 0 "${_args_num}"); do + # Replace empty string. + printf "%s\\n" "${_args[i]:-${_unprintable_str}}" + done | xpns_log_filenames "${_log_format}" ) } ## Print "1" on the particular named pipe xpns_notify() { - local _wait_id="$1" ; shift + local _wait_id="$1" + shift local _fifo= _fifo="${XP_CACHE_HOME}/__xpns_${_wait_id}" xpns_msg_debug "Notify to $_fifo" @@ -647,7 +653,8 @@ xpns_notify() { } xpns_notify_logging() { - local _window_name="$1" ; shift + local _window_name="$1" + shift local _args_num=$(($# - 1)) for i in $(xpns_seq 0 "${_args_num}"); do xpns_notify "log_${_window_name}-${i}-$$" @@ -655,7 +662,8 @@ xpns_notify_logging() { } xpns_notify_sync() { - local _window_name="$1" ; shift + local _window_name="$1" + shift local _args_num=$(($# - 1)) for i in $(xpns_seq 0 "${_args_num}"); do xpns_notify "sync_${_window_name}-${i}-$$" & @@ -663,12 +671,15 @@ xpns_notify_sync() { } xpns_is_window_alive() { - local _window_name="$1" ;shift - local _speedy_await_flag="$1" ;shift - local _def_allow_rename="$1" ;shift - if ! ${TMUX_XPANES_EXEC} display-message -t "$_window_name" -p > /dev/null 2>&1 ;then + local _window_name="$1" + shift + local _speedy_await_flag="$1" + shift + local _def_allow_rename="$1" + shift + if ! ${TMUX_XPANES_EXEC} display-message -t "$_window_name" -p > /dev/null 2>&1; then xpns_msg_info "All the panes are closed before displaying the result." - if [[ "${_speedy_await_flag}" -eq 0 ]] ;then + if [[ "${_speedy_await_flag}" -eq 0 ]]; then xpns_msg_info "Use '-s' option instead of '-ss' option to avoid this behavior." fi xpns_restore_allow_rename "${_def_allow_rename-}" @@ -677,24 +688,28 @@ xpns_is_window_alive() { } xpns_inject_title() { - local _target_pane="$1" ;shift - local _message="$1" ;shift + local _target_pane="$1" + shift + local _message="$1" + shift local _pane_tty= - _pane_tty="$( ${TMUX_XPANES_EXEC} display-message -t "${_target_pane}" -p "#{pane_tty}" )" + _pane_tty="$( ${TMUX_XPANES_EXEC} display-message -t "${_target_pane}" -p "#{pane_tty}")" printf "\\033]2;%s\\033\\\\" "${_message}" > "${_pane_tty}" xpns_msg_debug "target_pane=${_target_pane} pane_title=${_message} pane_tty=${_pane_tty}" } xpns_is_pane_title_required() { - local _title_flag="$1" ; shift - local _extra_flag="$1" ; shift + local _title_flag="$1" + shift + local _extra_flag="$1" + shift local _pane_border_status= _pane_border_status=$(xpns_get_local_tmux_conf "pane-border-status") if [[ $_title_flag -eq 1 ]]; then return 0 - elif [[ ${_extra_flag} -eq 1 ]] && \ - [[ "${_pane_border_status}" != "off" ]] && \ - [[ -n "${_pane_border_status}" ]] ;then + elif [[ ${_extra_flag} -eq 1 ]] && + [[ "${_pane_border_status}" != "off" ]] && + [[ -n "${_pane_border_status}" ]]; then ## For -x option # Even the -t option is not specified, it is required to inject pane title here. # Because user expects the title is displayed on the pane if the original window is @@ -706,39 +721,44 @@ xpns_is_pane_title_required() { # Set pane titles for each pane for -t option xpns_set_titles() { - local _window_name="$1" ; shift - local _index_offset="$1" ; shift + local _window_name="$1" + shift + local _index_offset="$1" + shift local _index=0 local _pane_index= - for arg in "$@" - do - _pane_index=$(( _index + _index_offset )) + for arg in "$@"; do + _pane_index=$((_index + _index_offset)) xpns_inject_title "${_window_name}.${_pane_index}" "${arg}" - _index=$(( _index + 1 )) + _index=$((_index + 1)) done } # Send command to the all the panes in the target window. xpns_send_commands() { - local _window_name="$1" ; shift - local _index_offset="$1" ; shift - local _repstr="$1" ; shift - local _cmd="$1" ; shift + local _window_name="$1" + shift + local _index_offset="$1" + shift + local _repstr="$1" + shift + local _cmd="$1" + shift local _index=0 local _pane_index= local _exec_cmd= - for arg in "$@" - do + for arg in "$@"; do _exec_cmd="${_cmd//${_repstr}/${arg}}" - _pane_index=$(( _index + _index_offset )) + _pane_index=$((_index + _index_offset)) ${TMUX_XPANES_EXEC} send-keys -t "${_window_name}.${_pane_index}" "${_exec_cmd}" C-m - _index=$(( _index + 1 )) + _index=$((_index + 1)) done } # Separate window vertically, when the number of panes is 1 or 2. xpns_organize_panes() { - local _window_name="$1" ; shift + local _window_name="$1" + shift local _args_num="$1" ## ---------------- # Default behavior @@ -759,14 +779,14 @@ xpns_organize_panes() { # # Generate sequential number descending order. # seq is not used because old version of -# seq does not generate descending oorder. +# seq does not generate descending order. # $ xpns_seq 3 0 # 3 # 2 # 1 # 0 # -xpns_seq () { +xpns_seq() { local _num1="$1" local _num2="$2" eval "printf \"%d\\n\" {$_num1..$_num2}" @@ -787,21 +807,28 @@ xpns_wait_func() { # Split a new window into multiple panes. # xpns_split_window() { - local _window_name="$1" ; shift - local _log_flag="$1" ; shift - local _title_flag="$1" ; shift - local _speedy_flag="$1" ; shift - local _await_flag="$1" ; shift - local _pane_base_index="$1" ; shift - local _repstr="$1" ; shift - local _cmd_template="$1" ; shift + local _window_name="$1" + shift + local _log_flag="$1" + shift + local _title_flag="$1" + shift + local _speedy_flag="$1" + shift + local _await_flag="$1" + shift + local _pane_base_index="$1" + shift + local _repstr="$1" + shift + local _cmd_template="$1" + shift local _exec_cmd= local _sep_count=0 local args=("$@") - _last_idx=$(( ${#args[@]} - 1 )) + _last_idx=$((${#args[@]} - 1)) - for i in $(xpns_seq $_last_idx 0) - do + for i in $(xpns_seq $_last_idx 0); do xpns_msg_debug "Index:${i} Argument:${args[i]}" _sep_count=$((_sep_count + 1)) _exec_cmd="${_cmd_template//${_repstr}/${args[i]}}" @@ -825,17 +852,23 @@ xpns_split_window() { } # -# Create new panes to the existing window. +# Create new panes on existing window. # Usage: # func # xpns_prepare_extra_panes() { - local _window_name="$1" ; shift - local _pane_base_index="$1" ; shift - local _log_flag="$1" ; shift - local _title_flag="$1" ; shift - local _speedy_flg="$1" ; shift - local _await_flg="$1" ; shift + local _window_name="$1" + shift + local _pane_base_index="$1" + shift + local _log_flag="$1" + shift + local _title_flag="$1" + shift + local _speedy_flg="$1" + shift + local _await_flg="$1" + shift # specify a pane which has the biggest index number. # Because pane_id may not be immutable. # If the small number of index is specified here, correspondance between pane_title and command can be slip off. @@ -852,7 +885,7 @@ xpns_prepare_extra_panes() { "$@" } -xpns_get_joined_begin_commands () { +xpns_get_joined_begin_commands() { local _commands="$1" if [[ "${#XP_BEGIN_ARGS[*]}" -lt 1 ]]; then printf "%s" "${_commands}" @@ -861,13 +894,19 @@ xpns_get_joined_begin_commands () { printf "%s\\n" "${XP_BEGIN_ARGS[@]}" "${_commands}" } -xpns_inject_wait_command () { - local _log_flag="$1" ; shift - local _title_flag="$1" ; shift - local _speedy_flg="$1" ; shift - local _await_flg="$1" ; shift - local _idx="$1" ; shift - local _exec_cmd="$1" ; shift +xpns_inject_wait_command() { + local _log_flag="$1" + shift + local _title_flag="$1" + shift + local _speedy_flg="$1" + shift + local _await_flg="$1" + shift + local _idx="$1" + shift + local _exec_cmd="$1" + shift ## Speedy mode + logging if [[ "${_log_flag}" -eq 1 ]] && [[ "${_speedy_flg}" -eq 1 ]]; then @@ -891,11 +930,15 @@ xpns_inject_wait_command () { printf "%s" "${_exec_cmd}" } -xpns_new_window () { - local _window_name="$1" ; shift - local _attach_flg="$1" ; shift - local _speedy_flg="$1" ; shift - local _exec_cmd="$1" ; shift +xpns_new_window() { + local _window_name="$1" + shift + local _attach_flg="$1" + shift + local _speedy_flg="$1" + shift + local _exec_cmd="$1" + shift local _window_id= # Create new window. @@ -903,7 +946,7 @@ xpns_new_window () { if [[ "${_speedy_flg}" -eq 1 ]]; then _window_id=$(${TMUX_XPANES_EXEC} new-window -n "${_window_name}" -F '#{window_id}' -P "${_exec_cmd}") else - _window_id=$(${TMUX_XPANES_EXEC} new-window -n "${_window_name}" -F '#{window_id}' -P ) + _window_id=$(${TMUX_XPANES_EXEC} new-window -n "${_window_name}" -F '#{window_id}' -P) fi else # Keep background @@ -916,11 +959,15 @@ xpns_new_window () { printf "%s" "${_window_id}" } -xpns_new_pane_vertical () { - local _window_id="$1" ; shift - local _cell_height="$1" ; shift - local _speedy_flg="$1" ; shift - local _exec_cmd="$1" ; shift +xpns_new_pane_vertical() { + local _window_id="$1" + shift + local _cell_height="$1" + shift + local _speedy_flg="$1" + shift + local _exec_cmd="$1" + shift local _pane_id= if [[ "${_speedy_flg}" -eq 1 ]]; then _pane_id="$(${TMUX_XPANES_EXEC} split-window -t "$_window_id" -v -d -l "${_cell_height}" -F '#{pane_id}' -P "${_exec_cmd}")" @@ -930,11 +977,15 @@ xpns_new_pane_vertical () { printf "%s\\n" "${_pane_id}" } -xpns_split_pane_horizontal () { - local _target_pane_id="$1" ; shift - local _cell_width="$1" ; shift - local _speedy_flg="$1" ; shift - local _exec_cmd="$1" ; shift +xpns_split_pane_horizontal() { + local _target_pane_id="$1" + shift + local _cell_width="$1" + shift + local _speedy_flg="$1" + shift + local _exec_cmd="$1" + shift if [[ "${_speedy_flg}" -eq 1 ]]; then ${TMUX_XPANES_EXEC} split-window -t "$_target_pane_id" -h -d -l "$_cell_width" "${_exec_cmd}" else @@ -942,15 +993,23 @@ xpns_split_pane_horizontal () { fi } -xpns_prepare_window () { - local _window_name="$1" ; shift - local _log_flag="$1" ; shift - local _title_flag="$1" ; shift - local _attach_flg="$1" ; shift - local _speedy_flg="$1" ; shift - local _await_flg="$1" ; shift - local _repstr="$1" ; shift - local _cmd_template="$1" ; shift +xpns_prepare_window() { + local _window_name="$1" + shift + local _log_flag="$1" + shift + local _title_flag="$1" + shift + local _attach_flg="$1" + shift + local _speedy_flg="$1" + shift + local _await_flg="$1" + shift + local _repstr="$1" + shift + local _cmd_template="$1" + shift local _args=("$@") local _window_height="$XP_WINDOW_HEIGHT" local _window_width="$XP_WINDOW_WIDTH" @@ -972,7 +1031,7 @@ xpns_prepare_window () { local _rest_row= local _offset= - _cell_height=$(( ( _window_height - _row + 1 ) / _row )) + _cell_height=$(((_window_height - _row + 1) / _row)) ## Insert first element _exec_cmd="${_cmd_template//${_repstr}/${_args[0]}}" _exec_cmd="$(xpns_inject_wait_command "${_log_flag}" "${_title_flag}" "${_speedy_flg}" "${_await_flg}" 0 "${_exec_cmd}")" @@ -980,14 +1039,14 @@ xpns_prepare_window () { _first_pane_id=$(${TMUX_XPANES_EXEC} display-message -t "$_window_id" -p -F '#{pane_id}' | head -n 1) ## Start from last row - for (( i = _row - 1 ; i > 0 ; i-- ));do + for ((i = _row - 1; i > 0; i--)); do _col="${_cols[i]}" - _cell_width=$(( ( _window_width - _col + 1 ) / _col )) + _cell_width=$(((_window_width - _col + 1) / _col)) xpns_msg_debug "_col=$_col" - (( _offset = _cols_offset[i] )) - for (( j = 0 ; j < _col ; j++ ));do - if (( j == 0 )) ;then - (( idx = _offset - _col )) + ((_offset = _cols_offset[i])) + for ((j = 0; j < _col; j++)); do + if ((j == 0)); then + ((idx = _offset - _col)) # Create new row # Insert first element of the row first _exec_cmd="${_cmd_template//${_repstr}/${_args[idx]}}" @@ -995,34 +1054,34 @@ xpns_prepare_window () { _pane_id=$(xpns_new_pane_vertical "${_window_name}" "${_cell_height}" "${_speedy_flg}" "${_exec_cmd}") fi # Separate row into columns - if (( j != 0 )) ;then - (( idx = _offset - j )) + if ((j != 0)); then + ((idx = _offset - j)) _exec_cmd="${_cmd_template//${_repstr}/${_args[idx]}}" _exec_cmd="$(xpns_inject_wait_command "${_log_flag}" "${_title_flag}" "${_speedy_flg}" "${_await_flg}" "${idx}" "${_exec_cmd}")" ## Separate row into columns _current_pane_width=$(${TMUX_XPANES_EXEC} display-message -t "$_pane_id" -p '#{pane_width}' | head -n 1) - _rest_col=$(( _col - j + 1 )) - _cell_width=$(( ( _current_pane_width - _rest_col + 1 ) / _rest_col )) + _rest_col=$((_col - j + 1)) + _cell_width=$(((_current_pane_width - _rest_col + 1) / _rest_col)) xpns_split_pane_horizontal "$_pane_id" "$_cell_width" "${_speedy_flg}" "${_exec_cmd}" fi done # Adjust height _top_pane_height=$(${TMUX_XPANES_EXEC} display-message -t "$_window_id" -p '#{pane_height}' | head -n 1) - _rest_row=$(( i )) + _rest_row=$((i)) xpns_msg_debug "_top_pane_height=$_top_pane_height _rest_row=$_rest_row" - _cell_height=$(( ( _top_pane_height - _rest_row + 1 ) / _rest_row )) + _cell_height=$(((_top_pane_height - _rest_row + 1) / _rest_row)) done # Split first row into columns _col="${_cols[0]}" - _cell_width=$(( ( _window_width - _col + 1 ) / _col )) - for (( j = 1 ; j < _col ; j++ ));do - idx=$(( _cols_offset[0] - j )) + _cell_width=$(((_window_width - _col + 1) / _col)) + for ((j = 1; j < _col; j++)); do + idx=$((_cols_offset[0] - j)) # Adjust width _current_pane_width=$(${TMUX_XPANES_EXEC} display-message -t "$_first_pane_id" -p '#{pane_width}' | head -n 1) - _rest_col=$(( _col - j + 1 )) - _cell_width=$(( ( _current_pane_width - _rest_col + 1 ) / _rest_col )) + _rest_col=$((_col - j + 1)) + _cell_width=$(((_current_pane_width - _rest_col + 1) / _rest_col)) ## Split top row into columns _exec_cmd="${_cmd_template//${_repstr}/${_args[idx]}}" _exec_cmd="$(xpns_inject_wait_command "${_log_flag}" "${_title_flag}" "${_speedy_flg}" "${_await_flg}" "${idx}" "${_exec_cmd}")" @@ -1036,21 +1095,21 @@ xpns_is_session_running() { } # Remove unnecessary session files as much as possible -# to let xpanes avoids to load old .tmux.conf. +# to make xpanes avoid loading old .tmux.conf. xpns_clean_session() { if [[ "${XP_SOCKET_PATH}" != "${XP_DEFAULT_SOCKET_PATH}" ]]; then return fi # Delete old socket file (xpanes v3.1.0 or before). if [[ -e "${XP_DEFAULT_SOCKET_PATH_BASE}" ]]; then - if ! xpns_is_session_running "${XP_DEFAULT_SOCKET_PATH_BASE}" ;then + if ! xpns_is_session_running "${XP_DEFAULT_SOCKET_PATH_BASE}"; then xpns_msg_debug "socket(${XP_DEFAULT_SOCKET_PATH_BASE}) is not running. Remove it" rm -f "${XP_DEFAULT_SOCKET_PATH_BASE}" fi fi - for _socket in "${XP_CACHE_HOME}"/socket.* ;do + for _socket in "${XP_CACHE_HOME}"/socket.*; do xpns_msg_debug "file = ${_socket}" - if ! xpns_is_session_running "${_socket}" ;then + if ! xpns_is_session_running "${_socket}"; then xpns_msg_debug "socket(${_socket}) is not running. Remove it" rm -f "${_socket}" else @@ -1060,18 +1119,25 @@ xpns_clean_session() { } # -# Split a new window which was created by tmux into multiple panes. +# Split a new window into multiple panes. # Usage: # xpns_prepare_preset_layout_window # xpns_prepare_preset_layout_window() { - local _window_name="$1" ; shift - local _pane_base_index="$1" ; shift - local _log_flag="$1" ; shift - local _title_flag="$1" ; shift - local _attach_flg="$1" ; shift - local _speedy_flg="$1" ; shift - local _await_flg="$1" ; shift + local _window_name="$1" + shift + local _pane_base_index="$1" + shift + local _log_flag="$1" + shift + local _title_flag="$1" + shift + local _attach_flg="$1" + shift + local _speedy_flg="$1" + shift + local _await_flg="$1" + shift # Create new window. if [[ "${_attach_flg}" -eq 1 ]]; then ${TMUX_XPANES_EXEC} new-window -n "${_window_name}" @@ -1107,7 +1173,7 @@ xpns_prepare_preset_layout_window() { # Check whether given command is in the PATH or not. xpns_check_env() { local _cmds="$1" - while read -r cmd ; do + while read -r cmd; do if ! type "${cmd}" > /dev/null 2>&1; then if [[ "${cmd}" == "tmux" ]] && [[ "${TMUX_XPANES_EXEC}" == "tmux" ]]; then xpns_msg_error "${cmd} is required. Install ${cmd} or set TMUX_XPANES_EXEC variable." @@ -1119,7 +1185,7 @@ xpns_check_env() { fi done < <(echo "${_cmds}" | tr ' ' '\n') - if ! mkdir -p "${XP_CACHE_HOME}";then + if ! mkdir -p "${XP_CACHE_HOME}"; then xpns_msg_warning "failed to create cache directory '${XP_CACHE_HOME}'." fi @@ -1127,11 +1193,11 @@ xpns_check_env() { TMUX_XPANES_TMUX_VERSION="${TMUX_XPANES_TMUX_VERSION:-$(xpns_get_tmux_version)}" if ( xpns_tmux_is_greater_equals \ "${XP_SUPPORT_TMUX_VERSION_LOWER}" \ - "${TMUX_XPANES_TMUX_VERSION}" ) ;then + "${TMUX_XPANES_TMUX_VERSION}" ); then : "Supported tmux version" else xpns_msg_warning \ -"'${XP_THIS_FILE_NAME}' may not work correctly! Please check followings. + "'${XP_THIS_FILE_NAME}' may not work correctly! Please check followings. * tmux is installed correctly. * Supported tmux version is installed. Version ${XP_SUPPORT_TMUX_VERSION_LOWER} and over is officially supported." @@ -1149,13 +1215,48 @@ xpns_pipe_filter() { fi } -xpns_set_args_per_pane() { - local _pane_num="$1"; shift +# Merge array's element by combining with space. +# i.e +# array=(1 2 3 4 5) +# func 3 "array" +# => array is going to be ("1 2 3" "4 5") +xpns_merge_array_elements() { + local _line="" + local _pane_num="$1" + shift + local _arr_name="$1" + shift local _filtered_args=() - while read -r _line; do + eval 'set -- "${'"$_arr_name"'[@]}"' + local _num="$#" + for ((i = 1; i <= _num; i++)); do + if [[ -z "$_line" ]]; then + _line="$1" + else + _line="${_line}${XP_OFS}$1" + fi + shift + if ((i % _pane_num == 0)); then + _filtered_args+=("${_line}") + _line="" + fi + done + if [[ -n "$_line" ]]; then _filtered_args+=("${_line}") - done < <(xargs -n "${_pane_num}" <<<"$(xpns_arr2args "${XP_ARGS[@]}")") - XP_ARGS=("${_filtered_args[@]}") + fi + eval "$_arr_name"'=("${_filtered_args[@]}")' +} + +xpns_newline2space() { + local _result="" + while read -r _line; do + if [[ -z "$_result" ]]; then + _result="$_line" + else + _result="${_result}${XP_OFS}${_line}" + fi + done + printf "%s\\n" "${_result}" } xpns_get_window_height_width() { @@ -1177,7 +1278,7 @@ xpns_get_window_height_width() { fi fi if [[ $XP_IS_PIPE_MODE -eq 0 ]]; then - if _result=$(stty size 2> /dev/null) && [[ "$_result" =~ $_pattern ]];then + if _result=$(stty size 2> /dev/null) && [[ "$_result" =~ $_pattern ]]; then _height="${BASH_REMATCH[1]}" _width="${BASH_REMATCH[2]}" xpns_msg_debug "window height: $_height, width: $_width" @@ -1185,13 +1286,16 @@ xpns_get_window_height_width() { return 0 fi else - if ! type ps > /dev/null 2>&1 ;then + if ! type ps > /dev/null 2>&1; then xpns_msg_debug "'ps' does not exist: Failed to get window height and size. Skip checking" return 1 fi - { read -r; read -r _dev; } < <(ps -o tty -p $$) + { + read -r # Remove first line + read -r _dev + } < <(ps -o tty -p $$ 2> /dev/null) ## If it's Linux, -F option is used - if _result=$(stty -F "/dev/${_dev}" size 2> /dev/null) && [[ "$_result" =~ $_pattern ]];then + if _result=$(stty -F "/dev/${_dev}" size 2> /dev/null) && [[ "$_result" =~ $_pattern ]]; then _height="${BASH_REMATCH[1]}" _width="${BASH_REMATCH[2]}" xpns_msg_debug "window height: $_height, width: $_width" @@ -1199,7 +1303,7 @@ xpns_get_window_height_width() { return 0 fi ## If it's BSD, macOS, -F option is used - if _result=$(stty -f "/dev/${_dev}" size 2> /dev/null) && [[ "$_result" =~ $_pattern ]];then + if _result=$(stty -f "/dev/${_dev}" size 2> /dev/null) && [[ "$_result" =~ $_pattern ]]; then _height="${BASH_REMATCH[1]}" _width="${BASH_REMATCH[2]}" xpns_msg_debug "window height: $_height, width: $_width" @@ -1212,11 +1316,16 @@ xpns_get_window_height_width() { } xpns_check_cell_size_bulk() { - local _cell_num="$1" ; shift - local _bulk_cols="$1" ; shift - local _win_height="$1" ; shift - local _win_width="$1" ; shift - local _ignore_flag="$1" ; shift + local _cell_num="$1" + shift + local _bulk_cols="$1" + shift + local _win_height="$1" + shift + local _win_width="$1" + shift + local _ignore_flag="$1" + shift local _all_cols=() # shellcheck disable=SC2178 local _cols=0 @@ -1225,16 +1334,16 @@ xpns_check_cell_size_bulk() { IFS="," read -r -a _all_cols <<< "${_bulk_cols}" _rows="${#_all_cols[@]}" for i in "${_all_cols[@]}"; do - (( i >= _cols )) && (( _cols = i )) - (( _sum_cell = _sum_cell + i )) + ((i >= _cols)) && ((_cols = i)) + ((_sum_cell = _sum_cell + i)) done - if (( _sum_cell != _cell_num )) ;then + if ((_sum_cell != _cell_num)); then xpns_msg_error "Number of cols does not equals to the number of arguments." xpns_msg_error "Expected (# of args) : $_cell_num, Actual (--bulk-cols) : $_sum_cell)." return ${XP_ELAYOUT:-6} fi - local cell_height=$(( ( _win_height - _rows + 1 ) / _rows )) - local cell_width=$(( ( _win_width - _cols + 1 ) / _cols )) + local cell_height=$(((_win_height - _rows + 1) / _rows)) + local cell_width=$(((_win_width - _cols + 1) / _cols)) ## Display basic information xpns_msg_debug "Window: { Height: $_win_height, Width: $_win_width }" @@ -1245,7 +1354,7 @@ xpns_check_cell_size_bulk() { xpns_msg_debug " Col[]| ${_all_cols[*]}" xpns_msg_debug " -----+------------------------..." - if [[ "$_ignore_flag" -ne 1 ]] && ( (( cell_height < 2 )) || (( cell_width < 2 )) ); then + if [[ "$_ignore_flag" -ne 1 ]] && ( ((cell_height < 2)) || ((cell_width < 2)) ); then xpns_msg_error "Expected pane size is too small (height: $cell_height lines, width: $cell_width chars)" return ${XP_ESMLPANE:-7} fi @@ -1253,26 +1362,32 @@ xpns_check_cell_size_bulk() { } xpns_check_cell_size() { - local _cell_num="$1" ; shift - local _cols="$1" ; shift - local _rows="$1" ; shift - local _win_height="$1" ; shift - local _win_width="$1" ; shift - local _ignore_flag="$1" ; shift + local _cell_num="$1" + shift + local _cols="$1" + shift + local _rows="$1" + shift + local _win_height="$1" + shift + local _win_width="$1" + shift + local _ignore_flag="$1" + shift local _all_cols_num= local _all_rows=() - if [[ -n "${_cols-}" ]] && [[ -n "${_rows-}" ]];then + if [[ -n "${_cols-}" ]] && [[ -n "${_rows-}" ]]; then xpns_msg_warning "Both col size and row size are provided. Col size is preferentially going to be applied." fi ## if col is only defined - if [[ -n "${_cols-}" ]] ;then + if [[ -n "${_cols-}" ]]; then read -r _cols _rows < <(xpns_adjust_col_row "${_cols-}" 0 "${_cell_num}") IFS=" " read -r -a _all_rows <<< "$(xpns_divide_equally "${_cell_num}" "${_cols}")" _all_cols_num="$(xpns_nums_transpose "${_all_rows[@]}")" ## if row is only defined - elif [[ -n "${_rows-}" ]] ;then + elif [[ -n "${_rows-}" ]]; then read -r _cols _rows < <(xpns_adjust_col_row 0 "${_rows-}" "${_cell_num}") _all_cols_num="$(xpns_divide_equally "${_cell_num}" "${_rows}")" @@ -1282,8 +1397,8 @@ xpns_check_cell_size() { _all_cols_num="$(xpns_divide_equally "${_cell_num}" "${_rows}")" fi - local cell_height=$(( ( _win_height - _rows + 1 ) / _rows )) - local cell_width=$(( ( _win_width - _cols + 1 ) / _cols )) + local cell_height=$(((_win_height - _rows + 1) / _rows)) + local cell_width=$(((_win_width - _cols + 1) / _cols)) ## Display basic information xpns_msg_debug "Window: { Height: $_win_height, Width: $_win_width }" @@ -1294,7 +1409,7 @@ xpns_check_cell_size() { xpns_msg_debug " Col[]| ${_all_cols_num}" xpns_msg_debug " -----+------------------------..." - if [[ "$_ignore_flag" -ne 1 ]] && ( (( cell_height < 2 )) || (( cell_width < 2 )) ); then + if [[ "$_ignore_flag" -ne 1 ]] && ( ((cell_height < 2)) || ((cell_width < 2)) ); then xpns_msg_error "Expected pane size is too small (height: $cell_height lines, width: $cell_width chars)" return "${XP_ESMLPANE:-7}" fi @@ -1306,7 +1421,7 @@ xpns_pre_execution() { local _opts4args="" local _args4args="" - if [[ ${XP_OPT_EXTRA} -eq 1 ]];then + if [[ ${XP_OPT_EXTRA} -eq 1 ]]; then xpns_msg_error "'-x' must be used within the running tmux session." exit ${XP_EINVAL} fi @@ -1316,8 +1431,8 @@ xpns_pre_execution() { IFS=" " read -r XP_WINDOW_HEIGHT XP_WINDOW_WIDTH < <(xpns_get_window_height_width) && { local _arg_num="${#XP_ARGS[@]}" local _cell_num _tmp_col_row_cols _tmp_cols - if [[ -n "$XP_MAX_PANE_ARGS" ]] && (( XP_MAX_PANE_ARGS > 1 ));then - _cell_num=$(( _arg_num / XP_MAX_PANE_ARGS )) + if [[ -n "$XP_MAX_PANE_ARGS" ]] && ((XP_MAX_PANE_ARGS > 1)); then + _cell_num=$((_arg_num / XP_MAX_PANE_ARGS)) else _cell_num="${_arg_num}" fi @@ -1372,13 +1487,13 @@ xpns_pre_execution() { # Avoid attaching (for unit testing). if [[ ${XP_OPT_ATTACH} -eq 1 ]]; then - if ! ${TMUX_XPANES_EXEC} -S "${XP_SOCKET_PATH}" attach-session -t "${XP_SESSION_NAME}" \ - && [[ ${XP_IS_PIPE_MODE} -eq 1 ]]; then + if ! ${TMUX_XPANES_EXEC} -S "${XP_SOCKET_PATH}" attach-session -t "${XP_SESSION_NAME}" && + [[ ${XP_IS_PIPE_MODE} -eq 1 ]]; then ## In recovery case, overwrite trap to keep socket file trap 'rm -f "${XP_CACHE_HOME}"/__xpns_*$$;' EXIT xpns_msg "Recovery" \ -"Execute below command line to re-attach the new session. + "Execute below command line to re-attach the new session. ${TMUX_XPANES_EXEC} -S ${XP_SOCKET_PATH} attach-session -t ${XP_SESSION_NAME} @@ -1396,8 +1511,8 @@ xpns_execution() { local _def_allow_rename= local _pane_count=0 - if [[ ${XP_IS_PIPE_MODE} -eq 0 ]] && [[ -n "${XP_MAX_PANE_ARGS-}" ]];then - xpns_set_args_per_pane "${XP_MAX_PANE_ARGS}" + if [[ ${XP_IS_PIPE_MODE} -eq 0 ]] && [[ -n "${XP_MAX_PANE_ARGS-}" ]]; then + xpns_merge_array_elements "${XP_MAX_PANE_ARGS}" 'XP_ARGS' fi ## Fix window size and define pane size @@ -1439,25 +1554,26 @@ xpns_execution() { xpns_suppress_allow_rename "${_def_allow_rename-}" XP_CMD_UTILITY="$(xpns_get_joined_begin_commands "${XP_CMD_UTILITY}")" - if [[ ${XP_OPT_EXTRA} -eq 1 ]];then + if [[ ${XP_OPT_EXTRA} -eq 1 ]]; then # Reuse existing window name # tmux 1.6 does not support -F option - _window_name="$( ${TMUX_XPANES_EXEC} display -p -F "#{window_id}" )" - _pane_count="$( ${TMUX_XPANES_EXEC} list-panes | grep -c . )" - _pane_base_index=$(( _pane_base_index + _pane_count - 1 )) + _window_name="$( ${TMUX_XPANES_EXEC} display -p -F "#{window_id}")" + _pane_count="$( ${TMUX_XPANES_EXEC} list-panes | grep -c .)" + _pane_base_index=$((_pane_base_index + _pane_count - 1)) _pane_active_pane_id=$(${TMUX_XPANES_EXEC} display -p -F "#{pane_id}") else _window_name=$( xpns_generate_window_name \ "${XP_EMPTY_STR}" \ - "${XP_ARGS[0]}" \ - | xpns_value2key) + "${XP_ARGS[0]}" | + xpns_value2key + ) fi ## -------------------- # Prepare window and panes ## -------------------- - if [[ ${XP_OPT_EXTRA} -eq 1 ]];then + if [[ ${XP_OPT_EXTRA} -eq 1 ]]; then xpns_prepare_extra_panes \ "${_window_name}" \ "${_pane_base_index}" \ @@ -1468,7 +1584,7 @@ xpns_execution() { "${XP_REPSTR}" \ "${XP_CMD_UTILITY}" \ "${XP_ARGS[@]}" - elif [[ ${XP_OPT_USE_PRESET_LAYOUT} -eq 1 ]];then + elif [[ ${XP_OPT_USE_PRESET_LAYOUT} -eq 1 ]]; then xpns_prepare_preset_layout_window \ "${_window_name}" \ "${_pane_base_index}" \ @@ -1480,7 +1596,7 @@ xpns_execution() { "${XP_REPSTR}" \ "${XP_CMD_UTILITY}" \ "${XP_ARGS[@]}" - elif [[ ${XP_OPT_USE_PRESET_LAYOUT} -eq 0 ]];then + elif [[ ${XP_OPT_USE_PRESET_LAYOUT} -eq 0 ]]; then xpns_prepare_window \ "${_window_name}" \ "${XP_OPT_LOG_STORE}" \ @@ -1494,11 +1610,11 @@ xpns_execution() { fi ## With -ss option, it is possible to close all the panes as of here. - ## Check status of the window. If no window exists, there is nothing to do execpt to exit. + ## Check status of the window. If no window exists, there is nothing to do any more and just exit. xpns_msg_debug "xpns_is_window_alive:1: After window separation" xpns_is_window_alive "${_window_name}" "${XP_OPT_SPEEDY_AWAIT}" "${_def_allow_rename-}" - if [[ ${XP_OPT_EXTRA} -eq 1 ]];then + if [[ ${XP_OPT_EXTRA} -eq 1 ]]; then # Set offset to avoid sending command to the original pane. _pane_base_index=$((_pane_base_index + 1)) # Avoid to make layout even-horizontal even if there are many panes. @@ -1528,14 +1644,14 @@ xpns_execution() { xpns_is_window_alive "${_window_name}" "${XP_OPT_SPEEDY_AWAIT}" "${_def_allow_rename-}" # Set pane titles for each pane. - if xpns_is_pane_title_required "${XP_OPT_SET_TITLE}" "${XP_OPT_EXTRA}" ;then + if xpns_is_pane_title_required "${XP_OPT_SET_TITLE}" "${XP_OPT_EXTRA}"; then xpns_set_titles \ "${_window_name}" \ "${_pane_base_index}" \ "${XP_ARGS[@]}" fi - if [[ $XP_OPT_SPEEDY -eq 1 ]];then + if [[ $XP_OPT_SPEEDY -eq 1 ]]; then xpns_notify_sync \ "${_window_name}" \ "${XP_ARGS[@]}" @@ -1586,7 +1702,7 @@ xpns_execution() { fi # In case of -x, this statement is skipped to keep the original window name - if [[ ${XP_OPT_EXTRA} -eq 0 ]];then + if [[ ${XP_OPT_EXTRA} -eq 0 ]]; then # Restore original window name. ${TMUX_XPANES_EXEC} \ rename-window -t "${_window_name}" \ @@ -1632,13 +1748,11 @@ xpns_switch_pipe_mode() { fi fi - while read -r line; - do + while read -r line; do XP_STDIN+=("${line}") - done < <(cat | xpns_rm_empty_line | \ + done < <(cat | xpns_rm_empty_line | xpns_pipe_filter "${_pane_num4new_term:-${XP_MAX_PANE_ARGS}}") - # Merge them into command. if [[ -n "${XP_ARGS[*]-}" ]]; then # Attention: It might be wrong result if IFS is changed. @@ -1678,7 +1792,7 @@ xpns_layout_short2long() { xpns_is_valid_layout() { local _layout="${1-}" local _pat='^(tiled|even-horizontal|even-vertical|main-horizontal|main-vertical)$' - if ! [[ $_layout =~ $_pat ]] ; then + if ! [[ $_layout =~ $_pat ]]; then xpns_msg_error "Invalid layout '${_layout}'." exit ${XP_ELAYOUT} fi @@ -1688,7 +1802,7 @@ xpns_warning_before_extra() { local _ans= local _synchronized= _synchronized="$(xpns_get_local_tmux_conf "synchronize-panes")" - if [[ "on" == "${_synchronized}" ]];then + if [[ "on" == "${_synchronized}" ]]; then xpns_msg_warning "Panes are now synchronized. '-x' option may cause unexpected behavior on the synchronized panes." printf "Are you really sure? [y/n]: " @@ -1699,289 +1813,276 @@ xpns_warning_before_extra() { fi } -xpns_load_flag_options() { - if [[ "$1" =~ h ]]; then - xpns_usage - exit 0 - fi - if [[ "$1" =~ V ]]; then - xpns_version - exit 0 - fi - if [[ "$1" =~ x ]]; then - XP_OPT_EXTRA=1 - XP_OPT_USE_PRESET_LAYOUT=1 ## Layout presets must be used with -x - if ! xpns_warning_before_extra; then - exit ${XP_EINTENT} - fi - fi - if [[ "$1" =~ d ]]; then - XP_OPT_IS_SYNC=0 - fi - if [[ "$1" =~ e ]]; then - XP_REPSTR="{}" - XP_CMD_UTILITY="{}" - fi - if [[ "$1" =~ t ]]; then - if ( xpns_tmux_is_greater_equals 2.3 ) ; then - XP_OPT_SET_TITLE=1 - XP_OPT_CHANGE_BORDER=1 - else - xpns_msg_warning "-t option cannot be used by tmux version less than 2.3. Disabled." - sleep 1 - fi - fi - if [[ "$1" =~ s ]]; then - XP_OPT_SPEEDY=1 - XP_OPT_SPEEDY_AWAIT=1 - fi - if [[ "$1" =~ ss ]]; then - XP_OPT_SPEEDY_AWAIT=0 +xpns_opt_check_num() { + local _option="$1" + shift + local _arg="$1" + if [[ -n "$_arg" ]] && [[ -z "${_arg//[0-9]/}" ]]; then + return 0 fi - return 1 + xpns_msg_error "Invalid argument '$_arg' for $_option option" + exit ${XP_EINVAL} } -xpns_load_arg_options() { - # Extract flag options only. - local _pattern= - xpns_load_flag_options "$(xpns_extract_matched "$1" "^-${XP_FLAG_OPTIONS}+")" > /dev/null - if [[ "$1" =~ ^-${XP_FLAG_OPTIONS}*I ]]; then - # Behavior like this. - # -IAAA -- XP_REPSTR="AAA" - # -I AAA BBB -- XP_REPSTR="AAA", XP_ARGS=("BBB") - # -I"AAA BBB" -- XP_REPSTR="AAA BBB" - # -IAAA BBB -- XP_REPSTR="AAA", XP_ARGS=("BBB") - # -I -d ... -- XP_REPSTR="" - _pattern="^-${XP_FLAG_OPTIONS}*I(.+)" - if [[ "$1" =~ $_pattern ]]; then - XP_REPSTR="${BASH_REMATCH[1]}" - return 0 - elif ! [[ "$2" =~ ^-.* ]]; then - XP_REPSTR="$2" - return 0 - else - xpns_msg_error "invalid argument '$2' for -I option" - exit ${XP_EINVAL} - fi - elif [[ "$1" =~ ^-${XP_FLAG_OPTIONS}*l ]]; then - _pattern="^-${XP_FLAG_OPTIONS}*l(.+)" - if [[ "$1" =~ $_pattern ]]; then - XP_OPT_USE_PRESET_LAYOUT=1 - XP_LAYOUT="$(cat <<<"${BASH_REMATCH[1]}" | xpns_layout_short2long)" - xpns_is_valid_layout "${XP_LAYOUT}" - return 0 - elif ! [[ "$2" =~ ^-.* ]]; then - XP_OPT_USE_PRESET_LAYOUT=1 - XP_LAYOUT="$(cat <<<"$2" | xpns_layout_short2long )" - xpns_is_valid_layout "${XP_LAYOUT}" - return 0 - else - xpns_msg_error "invalid argument '$2' for -l option" - exit ${XP_EINVAL} - fi - elif [[ "$1" =~ ^-${XP_FLAG_OPTIONS}*c ]]; then - _pattern="^-${XP_FLAG_OPTIONS}*c(.+)" - if [[ "$1" =~ $_pattern ]]; then - XP_CMD_UTILITY="${BASH_REMATCH[1]}" - XP_OPT_CMD_UTILITY=1 - return 0 - elif ! [[ "$2" =~ ^-.* ]]; then - XP_CMD_UTILITY="$2" - XP_OPT_CMD_UTILITY=1 - return 0 - else - xpns_msg_error "invalid argument '$2' for -c option" - exit ${XP_EINVAL} - fi - elif [[ "$1" =~ ^-${XP_FLAG_OPTIONS}*n ]]; then - _pattern="^-${XP_FLAG_OPTIONS}*n([0-9]+)" - if [[ "$1" =~ $_pattern ]]; then - XP_MAX_PANE_ARGS="${BASH_REMATCH[1]}" - return 0 - elif [[ "$2" =~ ^[0-9]+$ ]]; then - XP_MAX_PANE_ARGS="$2" - return 0 - else - xpns_msg_error "invalid argument '$2' for -n option" - exit ${XP_EINVAL} - fi - elif [[ "$1" =~ ^-${XP_FLAG_OPTIONS}*S ]]; then - _pattern="^-${XP_FLAG_OPTIONS}*S(.+)" - if [[ "$1" =~ $_pattern ]]; then - XP_SOCKET_PATH="${BASH_REMATCH[1]}" - return 0 - elif ! [[ "$2" =~ ^-.* ]]; then - XP_SOCKET_PATH="$2" - return 0 - else - xpns_msg_error "invalid argument '$2' for -S option" - exit ${XP_EINVAL} - fi - elif [[ "$1" =~ ^-${XP_FLAG_OPTIONS}*C ]]; then - _pattern="^-${XP_FLAG_OPTIONS}*C([0-9]+)" - if [[ "$1" =~ $_pattern ]]; then - XP_OPT_CUSTOM_SIZE_COLS="${BASH_REMATCH[1]}" - return 0 - elif [[ "$2" =~ ^[0-9]+$ ]];then - XP_OPT_CUSTOM_SIZE_COLS="$2" - return 0 - else - xpns_msg_error "invalid argument '$2' for -C option" - exit ${XP_EINVAL} - fi - elif [[ "$1" =~ ^-${XP_FLAG_OPTIONS}*R ]]; then - _pattern="^-${XP_FLAG_OPTIONS}*R([0-9]+)" - if [[ "$1" =~ $_pattern ]]; then - XP_OPT_CUSTOM_SIZE_ROWS="${BASH_REMATCH[1]}" - return 0 - elif [[ "$2" =~ ^[0-9]+$ ]];then - XP_OPT_CUSTOM_SIZE_ROWS="$2" - return 0 - else - xpns_msg_error "invalid argument '$2' for -R option" - exit ${XP_EINVAL} - fi - elif [[ "$1" =~ ^-${XP_FLAG_OPTIONS}*B ]]; then - _pattern="^-${XP_FLAG_OPTIONS}*B(.+)" - if [[ "$1" =~ $_pattern ]]; then - XP_BEGIN_ARGS+=("${BASH_REMATCH[1]}") - return 0 - else - XP_BEGIN_ARGS+=("$2") - return 0 - fi +xpns_opt_check_str() { + local _option="$1" + shift + local _arg="$1" + if [[ -n "$_arg" ]]; then + return 0 fi - return 0 + xpns_msg_error "Invalid argument '$_arg' for $_option option" + exit ${XP_EINVAL} } -xpns_load_long_options() { - if [[ "$1" =~ ^--help$ ]]; then - xpns_usage - exit 0 - elif [[ "$1" =~ ^--version$ ]]; then - xpns_version - exit 0 - elif [[ "$1" =~ ^--desync$ ]]; then - XP_OPT_IS_SYNC=0 - return 1 - elif [[ "$1" =~ ^--log-format=.*$ ]]; then - XP_OPT_LOG_STORE=1 - TMUX_XPANES_LOG_FORMAT="${1#--log-format=}" - return 1 - elif [[ "$1" =~ ^--log ]]; then - XP_OPT_LOG_STORE=1 - if [[ "$1" =~ ^--log=.*$ ]]; then - TMUX_XPANES_LOG_DIRECTORY="${1#--log=}" - fi - return 1 - elif [[ "$1" =~ ^--ssh$ ]]; then - XP_CMD_UTILITY="${XP_SSH_CMD_UTILITY}" - # Enable -t option as well - XP_OPT_SET_TITLE=1 - XP_OPT_CHANGE_BORDER=1 - # Enable -s option - XP_OPT_SPEEDY=1 - XP_OPT_SPEEDY_AWAIT=1 - return 1 - elif [[ "$1" =~ ^--stay$ ]]; then - XP_OPT_ATTACH=0 - return 1 - elif [[ "$1" =~ ^--cols=[0-9]+$ ]]; then - XP_OPT_CUSTOM_SIZE_COLS="${1#--cols=}" - return 1 - elif [[ "$1" =~ ^--rows=[0-9]+$ ]]; then - XP_OPT_CUSTOM_SIZE_ROWS="${1#--rows=}" - return 1 - elif [[ "$1" =~ ^--bulk-cols=[0-9,]*[0-9]+$ ]]; then - XP_OPT_BULK_COLS="${1#--bulk-cols=}" - return 1 - elif [[ "$1" =~ ^--debug$ ]]; then - XP_OPT_DEBUG=1 - return 1 - elif [[ "$1" =~ ^--dry-run$ ]]; then # For unit testing - XP_OPT_DRY_RUN=1 - return 1 - elif [[ "$1" =~ ^--ignore-size-limit$ ]]; then - XP_OPT_IGNORE_SIZE_LIMIT=1 - return 1 - - ## ---------------- - # Other options - ## ---------------- - else - xpns_msg_error "invalid option -- '${1#--}'" - xpns_usage_warn - exit ${XP_EINVAL} +xpns_opt_check_num_csv() { + local _option="$1" + shift + local _arg="$1" + if [[ "$1" =~ ^([0-9][0-9]*,?)+$ ]]; then + return 0 fi + xpns_msg_error "Invalid argument '$_arg' for $_option option" + exit ${XP_EINVAL} } xpns_parse_options() { - while (( $# > 0 )); do - case "$1" in + while (($# > 0)); do + opt="$1" + shift + if [[ ${XP_NO_OPT} -eq 1 ]]; then + XP_ARGS+=("$opt") + continue + fi + + ## Skip regularization if the arg is empty or --log= option + if [[ -n "$opt" ]] && [[ -n "${opt##--log=*}" ]]; then + ## -ovalue → -o value + if [[ -z "${opt##-${XP_ARG_OPTIONS}?*}" ]]; then + set -- "${opt#??}" ${1+"$@"} + opt="${opt%$1}" + ## -abc → -a -bc + elif [[ -z "${opt##-[!-]?*}" ]]; then + set -- "-${opt#??}" ${1+"$@"} + opt="${opt%${1#-}}" + ## --option=value → --option value + elif [[ -z "${opt##--*=*}" ]]; then + set -- "${opt#--*=}" ${1+"$@"} + opt="${opt%%=*}" + fi + fi + + case "$opt" in --) - if [[ ${XP_NO_OPT} -eq 1 ]]; then - XP_ARGS+=("$1") - shift - else # Disable any more options XP_NO_OPT=1 - shift - fi - ;; + ;; ## ---------------- # Long options ## ---------------- - --*) - if [[ ${XP_NO_OPT} -eq 1 ]]; then - XP_ARGS+=("$1") + --help) + xpns_usage + exit 0 + ;; + --version) + xpns_version + exit 0 + ;; + --desync) + XP_OPTIONS+=("$opt") + XP_OPT_IS_SYNC=0 + ;; + --log-format) + xpns_opt_check_str "$opt" "$1" + XP_OPTIONS+=("$opt") + XP_OPT_LOG_STORE=1 + TMUX_XPANES_LOG_FORMAT="$1" + XP_OPTIONS+=("$1") shift - else - local _shift_count="0" - xpns_load_long_options "$@" - _shift_count="$?" - [[ "${_shift_count}" = "1" ]] && XP_OPTIONS+=("$1") && shift - fi - ;; + ;; + --log=*) + XP_OPT_LOG_STORE=1 + XP_OPTIONS+=("$opt") + TMUX_XPANES_LOG_DIRECTORY="${opt#--log=}" + xpns_opt_check_str "${opt%=}" "$TMUX_XPANES_LOG_DIRECTORY" + ;; + --log) + XP_OPT_LOG_STORE=1 + XP_OPTIONS+=("$opt") + ;; + --ssh) + XP_OPTIONS+=("$opt") + XP_CMD_UTILITY="${XP_SSH_CMD_UTILITY}" + # Enable -t option + XP_OPT_SET_TITLE=1 + XP_OPT_CHANGE_BORDER=1 + # Enable -s option + XP_OPT_SPEEDY=1 + XP_OPT_SPEEDY_AWAIT=1 + ;; + --stay) + XP_OPTIONS+=("$opt") + XP_OPT_ATTACH=0 + ;; + --cols) + xpns_opt_check_num "$opt" "$1" + XP_OPTIONS+=("$opt") + XP_OPT_CUSTOM_SIZE_COLS="$1" + XP_OPTIONS+=("$1") + shift + ;; + --rows) + xpns_opt_check_num "$opt" "$1" + XP_OPTIONS+=("$opt") + XP_OPT_CUSTOM_SIZE_ROWS="$1" + XP_OPTIONS+=("$1") + shift + ;; + --bulk-cols) + xpns_opt_check_num_csv "$opt" "$1" + XP_OPTIONS+=("$opt") + XP_OPT_BULK_COLS="$1" + XP_OPTIONS+=("$1") + shift + ;; + --debug) + XP_OPTIONS+=("$opt") + XP_OPT_DEBUG=1 + ;; + --dry-run) + XP_OPTIONS+=("$opt") + XP_OPT_DRY_RUN=1 + ;; + --ignore-size-limit) + XP_OPTIONS+=("$opt") + XP_OPT_IGNORE_SIZE_LIMIT=1 + ;; ## ---------------- - # Short options + # Short options without argument ## ---------------- - -*) - if [[ ${XP_NO_OPT} -eq 1 ]]; then - XP_ARGS+=("$1") - shift - else - local _shift_count="0" - if [[ "$1" =~ ^-${XP_FLAG_OPTIONS}*${XP_ARG_OPTIONS}. ]];then - xpns_load_arg_options "$@" - XP_OPTIONS+=("$1") && shift - elif [[ "$1" =~ ^-${XP_FLAG_OPTIONS}*${XP_ARG_OPTIONS}$ ]] && [[ -n "${2-}" ]];then - xpns_load_arg_options "$@" - _shift_count="$?" - XP_OPTIONS+=("$1" "$2") && shift && shift - elif [[ "$1" =~ ^-${XP_FLAG_OPTIONS}+$ ]];then - xpns_load_flag_options "$1" - XP_OPTIONS+=("$1") && shift - ## ---------------- - # Other options - ## ---------------- + -h) + xpns_usage + exit 0 + ;; + -V) + xpns_version + exit 0 + ;; + -x) + XP_OPTIONS+=("$opt") + XP_OPT_EXTRA=1 + XP_OPT_USE_PRESET_LAYOUT=1 ## Layout presets must be used with -x + if ! xpns_warning_before_extra; then + exit ${XP_EINTENT} + fi + ;; + -d) + XP_OPTIONS+=("$opt") + XP_OPT_IS_SYNC=0 + ;; + -e) + XP_OPTIONS+=("$opt") + XP_REPSTR="{}" + XP_CMD_UTILITY="{}" + ;; + -t) + XP_OPTIONS+=("$opt") + if ( xpns_tmux_is_greater_equals 2.3 ); then + XP_OPT_SET_TITLE=1 + XP_OPT_CHANGE_BORDER=1 else - xpns_msg_error "Invalid option -- '${1#-}'" - xpns_usage_warn - exit ${XP_EINVAL} + xpns_msg_warning "-t option cannot be used by tmux version less than 2.3. Disabled." + sleep 1 fi - fi - ;; + ;; + -s) + XP_OPTIONS+=("$opt") + XP_OPT_SPEEDY=1 + XP_OPT_SPEEDY_AWAIT=1 + if [[ -z "${1#-s}" ]]; then + XP_OPT_SPEEDY_AWAIT=0 + XP_OPTIONS+=("$1") + shift + fi + ;; + ## ---------------- + # Short options with argument + ## ---------------- + -I) + xpns_opt_check_str "$opt" "$1" + XP_OPTIONS+=("$opt") + XP_REPSTR="$1" + XP_OPTIONS+=("$1") + shift + ;; + -l) + xpns_opt_check_str "$opt" "$1" + XP_OPTIONS+=("$opt") + XP_OPT_USE_PRESET_LAYOUT=1 + XP_LAYOUT="$(printf '%s\n' "$1" | xpns_layout_short2long)" + xpns_is_valid_layout "${XP_LAYOUT}" + XP_OPTIONS+=("$1") + shift + ;; + -c) + # -c allows empty + # xpns_opt_check_str "$opt" "$1" + XP_OPTIONS+=("$opt") + XP_CMD_UTILITY="$1" + XP_OPT_CMD_UTILITY=1 + XP_OPTIONS+=("$1") + shift + ;; + -n) + xpns_opt_check_num "$opt" "$1" + XP_OPTIONS+=("$opt") + XP_MAX_PANE_ARGS="$1" + XP_OPTIONS+=("$1") + shift + ;; + -S) + xpns_opt_check_str "$opt" "$1" + XP_OPTIONS+=("$opt") + XP_SOCKET_PATH="$1" + XP_OPTIONS+=("$1") + shift + ;; + -C) + xpns_opt_check_num "$opt" "$1" + XP_OPTIONS+=("$opt") + XP_OPT_CUSTOM_SIZE_COLS="$1" + XP_OPTIONS+=("$1") + shift + ;; + -R) + xpns_opt_check_num "$opt" "$1" + XP_OPTIONS+=("$opt") + XP_OPT_CUSTOM_SIZE_ROWS="$1" + XP_OPTIONS+=("$1") + shift + ;; + -B) + # -B allows empty + # xpns_opt_check_str "$opt" "$1" + XP_OPTIONS+=("$opt") + XP_BEGIN_ARGS+=("$1") + XP_OPTIONS+=("$1") + shift + ;; + -*) + xpns_msg_error "Invalid option -- '${opt}'" + xpns_usage_warn + exit ${XP_EINVAL} + ;; ## ---------------- # Other arguments ## ---------------- *) - XP_ARGS+=("$1") - XP_NO_OPT=1 - shift - ;; + XP_ARGS+=("$opt") + XP_NO_OPT=1 + ;; esac done - # If there is any standard input from pipe, # 1 line handled as 1 argument. if [[ ! -t 0 ]]; then @@ -1989,7 +2090,7 @@ xpns_parse_options() { xpns_switch_pipe_mode fi - # When no argument arr given, exit. + # When no argument is given, exit. if [[ -z "${XP_ARGS[*]-}" ]]; then xpns_msg_error "No arguments." xpns_usage_warn @@ -2012,10 +2113,9 @@ xpns_parse_options() { XP_REPSTR="${XP_REPSTR:-${XP_DEFAULT_REPSTR}}" # To set command on pre_execution, set -c option manually. - if [[ ${XP_OPT_CMD_UTILITY} -eq 0 ]];then + if [[ ${XP_OPT_CMD_UTILITY} -eq 0 ]]; then XP_OPTIONS+=("-c" "${XP_CMD_UTILITY}") fi - } ## -------------------------------- @@ -2034,8 +2134,8 @@ xpns_main() { # Validate log directory. if [[ ${XP_OPT_LOG_STORE} -eq 1 ]]; then TMUX_XPANES_LOG_DIRECTORY=$(xpns_normalize_directory "${TMUX_XPANES_LOG_DIRECTORY}") - xpns_is_valid_directory "${TMUX_XPANES_LOG_DIRECTORY}" && \ - TMUX_XPANES_LOG_DIRECTORY=$(cd "${TMUX_XPANES_LOG_DIRECTORY}" && pwd) + xpns_is_valid_directory "${TMUX_XPANES_LOG_DIRECTORY}" && + TMUX_XPANES_LOG_DIRECTORY=$(cd "${TMUX_XPANES_LOG_DIRECTORY}" && pwd) fi ## -------------------------------- # If current shell is outside of tmux session.