All patches and comments are welcome. Please squash your changes to logical
commits before using git-format-patch and git-send-email to
patches@git.madduck.net.
If you'd read over the Git project's submission guidelines and adhered to them,
I'd be especially grateful.
   2 readonly XP_SHELL="/usr/bin/env bash"
 
   4 # @Author Yamada, Yasuhiro
 
   8 readonly XP_VERSION="4.1.1"
 
  10 ## trap might be updated in 'xpns_pre_execution' function
 
  11 trap 'rm -f "${XP_CACHE_HOME}"/__xpns_*$$; rm -f "${XP_DEFAULT_SOCKET_PATH}"' EXIT
 
  13 ## --------------------------------
 
  15 ## --------------------------------
 
  16 # Undefined or General errors
 
  19 # Invalid option/argument
 
  28 # Impossible layout: Small pane
 
  29 readonly XP_ESMLPANE=7
 
  31 # Log related exit status is 2x.
 
  32 ## Could not create a directory.
 
  33 readonly XP_ELOGDIR=20
 
  35 ## Could not directory to store logs is not writable.
 
  36 readonly XP_ELOGWRITE=21
 
  38 # User's intentional exit is 3x
 
  39 ## User exit the process intentionally by following warning message.
 
  40 readonly XP_EINTENT=30
 
  42 ## All the panes are closed before processing due to user's options/command.
 
  43 readonly XP_ENOPANE=31
 
  45 # Necessary commands are not found
 
  46 readonly XP_ENOCMD=127
 
  50 # XP_THIS_FILE_NAME is supposed to be "xpanes".
 
  51 readonly XP_THIS_FILE_NAME="${0##*/}"
 
  52 readonly XP_THIS_DIR="$(cd "$(dirname "${BASH_SOURCE[0]:-${(%):-%N}}")" && pwd)"
 
  53 readonly XP_ABS_THIS_FILE_NAME="${XP_THIS_DIR}/${XP_THIS_FILE_NAME}"
 
  55 # Prevent cache directory being created under root / directory in any case.
 
  56 # This is quite rare case (but it can be happened).
 
  57 readonly XP_USER_HOME="${HOME:-/tmp}"
 
  59 # Basically xpanes follows XDG Base Direcotry Specification.
 
  60 # https://specifications.freedesktop.org/basedir-spec/basedir-spec-0.6.html
 
  61 XDG_CACHE_HOME="${XDG_CACHE_HOME:-${XP_USER_HOME}/.cache}"
 
  62 readonly XP_CACHE_HOME="${XDG_CACHE_HOME}/xpanes"
 
  64 # This is supposed to be xpanes-12345(PID)
 
  65 readonly XP_SESSION_NAME="${XP_THIS_FILE_NAME}-$$"
 
  66 # Temporary window name is tmp-12345(PID)
 
  67 readonly XP_TMP_WIN_NAME="tmp-$$"
 
  68 readonly XP_EMPTY_STR="EMPTY"
 
  70 readonly XP_SUPPORT_TMUX_VERSION_LOWER="1.8"
 
  72 # Check dependencies just in case.
 
  73 # Even POSIX compliant commands are only used in this program.
 
  74 # `xargs`, `sleep`, `mkfifo` are omitted because minimum functions can work without them.
 
  75 readonly XP_DEPENDENCIES="${XP_DEPENDENCIES:-tmux grep sed tr od echo touch printf cat sort pwd cd mkfifo}"
 
  77 ## --------------------------------
 
  78 # User customizable shell variables
 
  79 ## --------------------------------
 
  80 TMUX_XPANES_EXEC=${TMUX_XPANES_EXEC:-tmux}
 
  81 TMUX_XPANES_PANE_BORDER_FORMAT="${TMUX_XPANES_PANE_BORDER_FORMAT:-#[bg=green,fg=black] #T #[default]}"
 
  82 TMUX_XPANES_PANE_BORDER_STATUS="${TMUX_XPANES_PANE_BORDER_STATUS:-bottom}"
 
  83 TMUX_XPANES_PANE_DEAD_MESSAGE=${TMUX_XPANES_PANE_DEAD_MESSAGE:-'\033[41m\033[4m\033[30m Pane is dead: Press [Enter] to exit... \033[0m\033[39m\033[49m'}
 
  84 XP_DEFAULT_TMUX_XPANES_LOG_FORMAT="[:ARG:].log.%Y-%m-%d_%H-%M-%S"
 
  85 TMUX_XPANES_LOG_FORMAT="${TMUX_XPANES_LOG_FORMAT:-${XP_DEFAULT_TMUX_XPANES_LOG_FORMAT}}"
 
  86 XP_DEFAULT_TMUX_XPANES_LOG_DIRECTORY="${XP_CACHE_HOME}/logs"
 
  87 TMUX_XPANES_LOG_DIRECTORY="${TMUX_XPANES_LOG_DIRECTORY:-${XP_DEFAULT_TMUX_XPANES_LOG_DIRECTORY}}"
 
  89 ## --------------------------------
 
  91 ## --------------------------------
 
  92 # options which work individually.
 
  93 readonly XP_FLAG_OPTIONS="[hVdetxs]"
 
  94 # options which need arguments.
 
  95 readonly XP_ARG_OPTIONS="[ISclnCRB]"
 
  96 readonly XP_DEFAULT_LAYOUT="tiled"
 
  97 readonly XP_DEFAULT_REPSTR="{}"
 
  98 readonly XP_DEFAULT_CMD_UTILITY="echo {} "
 
  99 readonly XP_SSH_CMD_UTILITY="ssh -o StrictHostKeyChecking=no {} "
 
 110 XP_DEFAULT_SOCKET_PATH_BASE="${XP_CACHE_HOME}/socket"
 
 111 XP_DEFAULT_SOCKET_PATH="${XP_DEFAULT_SOCKET_PATH_BASE}.$$"
 
 112 XP_SOCKET_PATH="${XP_SOCKET_PATH:-${XP_DEFAULT_SOCKET_PATH}}"
 
 116 XP_LAYOUT="${XP_DEFAULT_LAYOUT}"
 
 119 XP_OPT_CHANGE_BORDER=0
 
 122 XP_OPT_SPEEDY_AWAIT=0
 
 123 XP_OPT_USE_PRESET_LAYOUT=0
 
 124 XP_OPT_CUSTOM_SIZE_COLS=
 
 125 XP_OPT_CUSTOM_SIZE_ROWS=
 
 132 XP_OPT_IGNORE_SIZE_LIMIT=0
 
 134 ## --------------------------------
 
 136 #   $1 -- Log level (i.e Warning, Error)
 
 139 #      xpanes:Error: invalid option.
 
 141 # This log format is created with reference to openssl's one.
 
 142 #   $ echo | openssl -a
 
 143 #   openssl:Error: '-a' is an invalid command.
 
 144 ## --------------------------------
 
 148   local _msg="${XP_THIS_FILE_NAME}:${_loglevel}: ${_msgbody}"
 
 149   printf "%s\\n" "${_msg}" >&2
 
 157   xpns_msg "Warning" "$1"
 
 161   if [[ $XP_OPT_DEBUG -eq 1 ]];then
 
 162     xpns_msg "Debug" "$(date "+[%F_%T]"):${FUNCNAME[1]}:$1"
 
 167   xpns_msg "Error" "$1"
 
 172   echo "Try '${XP_THIS_FILE_NAME} --help' for more information." >&2
 
 177 Usage: ${XP_THIS_FILE_NAME} [OPTIONS] [argument ...]
 
 178 Usage(Pipe mode): command ... | ${XP_THIS_FILE_NAME} [OPTIONS] [<command> ...]
 
 185   ${XP_THIS_FILE_NAME} [OPTIONS] [argument ...]
 
 188   command ... | ${XP_THIS_FILE_NAME} [OPTIONS] [<command> ...]
 
 191   -h,--help                    Display this help and exit.
 
 192   -V,--version                 Output version information and exit.
 
 193   -B <begin-command>           Run <begin-command> before processing <command> in each pane. Multiple options are allowed.
 
 194   -c <command>                 Set <command> to be executed in each pane. Default is \`echo {}\`.
 
 195   -d,--desync                  Make synchronize-panes option off in new window.
 
 196   -e                           Execute given arguments as is. Same as \`-c '{}'\`
 
 197   -I <repstr>                  Replacing one or more occurrences of <repstr> in command provided by -c or -B. Default is \`{}\`.
 
 198   -C NUM,--cols=NUM            Number of columns of window layout.
 
 199   -R NUM,--rows=NUM            Number of rows of window layout.
 
 200   -l <layout>                  Set the preset of window layout. Recognized layout arguments are:
 
 206   -n <number>                  Set the maximum number of <argument> taken for each pane.
 
 207   -s                           Speedy mode: Run command without opening an interactive shell.
 
 208   -ss                          Speedy mode AND close a pane automatically at the same time as process exiting.
 
 209   -S <socket-path>             Set a full alternative path to the server socket.
 
 210   -t                           Display each argument on the each pane's border as their title.
 
 211   -x                           Create extra panes in the current active window.
 
 212   --log[=<directory>]          Enable logging and store log files to ~/.cache/xpanes/logs or <directory>.
 
 213   --log-format=<FORMAT>        Make name of log files follow <FORMAT>. Default is \`${XP_DEFAULT_TMUX_XPANES_LOG_FORMAT}\`.
 
 214   --ssh                        Same as \`-t -s -c 'ssh -o StrictHostKeyChecking=no {}'\`.
 
 215   --stay                       Do not switch to new window.
 
 216   --bulk-cols=NUM1[,NUM2 ...]  Set number of columns on multiple rows (i.e, "2,2,2" represents 2 cols x 3 rows).
 
 217   --debug                      Print debug message.
 
 219 Copyright (c) 2019 Yamada, Yasuhiro
 
 220 Released under the MIT License.
 
 221 https://github.com/greymd/tmux-xpanes
 
 225 # Show version number
 
 227   echo "${XP_THIS_FILE_NAME} ${XP_VERSION}"
 
 230 # Get version number for tmux
 
 231 xpns_get_tmux_version() {
 
 232   local _tmux_version=""
 
 233   if ! ${TMUX_XPANES_EXEC} -V &> /dev/null; then
 
 234     # From tmux 0.9 to 1.3, there is no -V option.
 
 235     _tmux_version="tmux 0.9-1.3"
 
 237     _tmux_version="$( ${TMUX_XPANES_EXEC} -V )"
 
 239   ( read -r _ _ver; echo "${_ver}" ) <<<"${_tmux_version}"
 
 242 # Check whether the prefered tmux version is greater than host's tmux version.
 
 243 # $1 ... Prefered version.
 
 244 # $2 ... Host tmux version(optional).
 
 245 # In case of tmux version is 1.7, the result will be like this.
 
 246 # 0 is true, 1 is false.
 
 255 xpns_tmux_is_greater_equals() {
 
 256   local _check_version="$1"
 
 257   local _tmux_version="${2:-$(xpns_get_tmux_version)}"
 
 258   # Simple numerical comparison does not work because there is the version like "1.9a".
 
 259   if [[ "$( (echo "${_tmux_version}"; echo "${_check_version}") | sort -n | head -n 1)" != "${_check_version}" ]];then
 
 266 xpns_get_local_tmux_conf() {
 
 267   local _conf_name="$1"
 
 268   local _session="${2-}"
 
 270     if [[ -z "${_session-}" ]];then
 
 271       ${TMUX_XPANES_EXEC} show-window-options
 
 273       ${TMUX_XPANES_EXEC} -S "${_session}" show-window-options
 
 275   } | grep "^${_conf_name}" \
 
 276     | ( read -r _ _v; printf "%s\\n" "${_v}" )
 
 279 xpns_get_global_tmux_conf() {
 
 280   local _conf_name="$1"
 
 281   local _session="${2-}"
 
 283     if [[ -z "${_session-}" ]];then
 
 284       ${TMUX_XPANES_EXEC} show-window-options -g
 
 286       ${TMUX_XPANES_EXEC} -S "${_session}" show-window-options -g
 
 288   } | grep "^${_conf_name}" \
 
 289     | ( read -r _ _v; printf "%s\\n" "${_v}" )
 
 292 # Disable allow-rename because
 
 293 # window separation does not work correctly
 
 294 # if "allow-rename" option is on
 
 295 xpns_suppress_allow_rename () {
 
 296   local _default_allow_rename="$1"
 
 297   local _session="${2-}"
 
 298   if [[ "${_default_allow_rename-}" == "on"  ]]; then
 
 299     ## Temporary, disable "allow-rename"
 
 300     xpns_msg_debug "'allow-rename' option is 'off' temporarily."
 
 301     if [[ -z "${_session-}" ]];then
 
 302       ${TMUX_XPANES_EXEC} set-window-option -g allow-rename off
 
 304       ${TMUX_XPANES_EXEC} -S "${_session}" set-window-option -g allow-rename off
 
 309 # Restore default "allow-rename"
 
 310 # Do not write like 'xpns_restore_allow_rename "some value" "some value" > /dev/null'
 
 311 # In tmux 1.6, 'tmux set-window-option' might be stopped in case of redirection.
 
 312 xpns_restore_allow_rename () {
 
 313   local _default_allow_rename="$1"
 
 314   local _session="${2-}"
 
 315   if [[ "${_default_allow_rename-}" == "on"  ]]; then
 
 316     xpns_msg_debug "Restore original value of 'allow-rename' option."
 
 317     if [[ -z "${_session-}" ]];then
 
 318       ${TMUX_XPANES_EXEC} set-window-option -g allow-rename on
 
 320       ${TMUX_XPANES_EXEC} -S "${_session}" set-window-option -g allow-rename on
 
 327 # 11 / 2 = 5.5 => ceiling => 6
 
 329   local _divide="$1";shift
 
 331   printf "%s\\n" $(( ( _divide + _by - 1 ) / _by ))
 
 336 # Divide 10 into 3 parts as equally as possible.
 
 337 xpns_divide_equally () {
 
 338   local _number="$1";shift
 
 340   local _upper _lower _upper_count _lower_count
 
 341   _upper="$(xpns_ceiling "$_number" "$_count")"
 
 342   _lower=$(( _upper - 1 ))
 
 343   _lower_count=$(( _upper * _count - _number ))
 
 344   _upper_count=$(( _count - _lower_count ))
 
 345   eval "printf '${_upper} %.0s' {1..$_upper_count}"
 
 346   (( _lower_count > 0 )) && eval "printf '${_lower} %.0s' {1..$_lower_count}"
 
 349 # echo 3 3 3 3 | func
 
 351 xpns_nums_accumulate_sum () {
 
 356   done < <( cat | tr ' ' '\n')
 
 362 # For example, "3 2 2 2" represents following cell positions
 
 364 # 1 [] [] [] => 3 rows
 
 369 # After the transposition, it must be "4 4 1" which represents below
 
 371 # 1 [] [] [] [] => 4 rows
 
 372 # 2 [] [] [] [] => 4 rows
 
 374 xpns_nums_transpose () {
 
 378   xpns_msg_debug "column num = $_colnum, input = $*"
 
 379   _spaces="$(for i in "$@";do
 
 383   # 'for' statement does not work somehow
 
 384   _result="$(while read -r i; do
 
 385     ## This part is depending on the following 'cut' behavior
 
 386     ## $ echo 1234 | cut -c 5
 
 387     ## => result is supposed to be empty
 
 388     printf "%s\\n" "$_spaces" | cut -c "$i" | grep -c ' '
 
 389   done < <(xpns_seq 1 "${_colnum}") | xargs)"
 
 390   xpns_msg_debug "result = $_result"
 
 391   printf "%s\\n" "$_result"
 
 394 # Adjust size of columns and rows in accordance with given N
 
 395 # func <col> <row> <N>
 
 401 xpns_adjust_col_row() {
 
 402   local col="${1:-0}" ;shift
 
 403   local row="${1:-0}" ;shift
 
 407   (( col != 0 )) && fix_col_flg=1 || fix_col_flg=0
 
 408   (( row != 0 )) && fix_row_flg=1 || fix_row_flg=0
 
 410   # This is just a author (@greymd)'s preference.
 
 411   if (( fix_col_flg == 0 )) && (( fix_row_flg == 0 )) && (( N == 2)) ;then
 
 414     printf "%d %d\\n" "${col}" "${row}"
 
 418   # If both valures are provided, col is used.
 
 419   if (( fix_col_flg == 1 )) && (( fix_row_flg == 1 ));then
 
 423   # This algorhythm is almost same as tmux default
 
 424   #   https://github.com/tmux/tmux/blob/2.8/layout-set.c#L436
 
 425   while (( col * row < N )) ;do
 
 426     (( fix_row_flg != 1 )) && (( row = row + 1 ))
 
 427     if (( col * row < N ));then
 
 428       (( fix_col_flg != 1 )) &&  (( col = col + 1 ))
 
 431   printf "%d %d\\n" "${col}" "${row}"
 
 434 # Make each line unique by adding index number
 
 435 # echo aaa bbb ccc aaa ccc ccc | xargs -n 1 | xpns_unique_line
 
 443 # Eval is used because associative array is not supported before bash 4.2
 
 444 xpns_unique_line () {
 
 446   while read -r line; do
 
 447     _val_name="__xpns_hash_$(printf "%s" "${line}" | xpns_value2key)"
 
 448     # initialize variable
 
 449     eval "${_val_name}=\${${_val_name}:-0}"
 
 451     eval "${_val_name}=\$(( ++${_val_name} ))"
 
 452     printf "%s\\n" "${line}-$(eval printf "%s" "\$${_val_name}")"
 
 457 # Generate log file names from given arguments.
 
 459 #        echo <arg1> <arg2> ... | xpns_log_filenames <FORMAT>
 
 463 #        $ echo aaa bbb ccc aaa ccc ccc | xargs -n 1 | xpns_log_filenames '[:ARG:]_[:PID:]_%Y%m%d.log'
 
 464 #        aaa-1_1234_20160101.log
 
 465 #        bbb-1_1234_20160101.log
 
 466 #        ccc-1_1234_20160101.log
 
 467 #        aaa-2_1234_20160101.log
 
 468 #        ccc-2_1234_20160101.log
 
 469 #        ccc-3_1234_20160101.log
 
 471 xpns_log_filenames () {
 
 474   _full_fmt="$(date "+${_arg_fmt}")"
 
 477     # 1st argument + '-' + unique number (avoid same argument has same name)
 
 481       cat <<<"${_full_fmt}" \
 
 482         | sed "s/\\[:ARG:\\]/${_arg}/g" \
 
 483         | sed "s/\\[:PID:\\]/$$/g"
 
 487 ## --------------------------------
 
 488 # Normalize directory by making following conversion.
 
 490 #  * Remove the slash '/' at the end of the dirname.
 
 492 #        xpns_normalize_directory <direname>
 
 494 #        Normalized <dirname>
 
 495 ## --------------------------------
 
 496 xpns_normalize_directory() {
 
 498   # Remove end of slash '/'
 
 501   _dir="${_dir/#~/${HOME}}"
 
 502   printf "%s\\n" "${_dir}"
 
 505 ## --------------------------------
 
 506 # Ensure existence of given directory
 
 508 #        xpns_is_valid_directory <direname>
 
 510 #        Absolute path of the <dirname>
 
 511 ## --------------------------------
 
 512 xpns_is_valid_directory() {
 
 514   local _checkfile="${XP_THIS_FILE_NAME}.$$"
 
 516   if [[ ! -d "${_dir}" ]]; then
 
 518     if mkdir "${_dir}"; then
 
 519       xpns_msg_info "${_dir} is created."
 
 521       xpns_msg_error "Failed to create ${_dir}"
 
 525   # Try to create file.
 
 526   #   Not only checking directory permission,
 
 527   #   but also i-node and other misc situations.
 
 528   if ! touch "${_dir}/${_checkfile}"; then
 
 529     xpns_msg_error "${_dir} is not writable."
 
 530     rm -f "${_dir}/${_checkfile}"
 
 533   rm -f "${_dir}/${_checkfile}"
 
 536 # Convert array to string which is can be used as command line argument.
 
 538 #       xpns_arr2args <array object>
 
 540 #       array=(aaa bbb "ccc ddd" eee "f'f")
 
 541 #       xpns_arr2args "${array[@]}"
 
 542 #       @returns "'aaa' 'bbb' 'ccc ddd' 'eee' 'f\'f'"
 
 546   # If there is no argument, usage will be shown.
 
 547   if [[ $# -lt 1 ]]; then
 
 552     # Use 'cat <<<"input"' command instead of 'echo',
 
 553     # because such the command recognizes option like '-e'.
 
 556       # Escaping single quotations.
 
 557       sed "s/'/'\"'\"'/g" \
 
 559       # Surround argument with single quotations.
 
 560       sed "s/^/'/;s/$/' /" \
 
 567 # Extract first field to generate window name.
 
 568 # ex, $2     =  'aaa bbb ccc'
 
 569 #   return   =  aaa-12345(PID)
 
 570 xpns_generate_window_name() {
 
 571   local _unprintable_str="${1-}"; shift
 
 572   # Leave first 200 characters to prevent
 
 573   # the name exceed the maximum length of tmux window name (2000 byte).
 
 574   printf "%s\\n" "${1:-${_unprintable_str}}" \
 
 575     | ( read -r _name _ && printf "%s\\n" "${_name:0:200}-$$" )
 
 578 # Convert string to another string which can be handled as tmux window name.
 
 580   od -v -tx1 -An  | tr -dc 'a-zA-Z0-9' | tr -d '\n'
 
 583 # Restore string encoded by xpns_value2key function.
 
 586   # shellcheck disable=SC2059
 
 587   printf "$(printf "%s" "$_key" | sed 's/../\\x&/g')"
 
 591 # This function behaves like `awk NF`
 
 592 xpns_rm_empty_line() {
 
 593   { cat; printf "\\n";} | while IFS= read -r line;do
 
 594     # shellcheck disable=SC2086
 
 596     if [[ $# != 0 ]]; then
 
 597       printf "%s\\n" "${line}"
 
 602 # Extract matched patterns from string
 
 603 # $ xpns_extract_matched "aaa123bbb" "[0-9]{3}"
 
 605 xpns_extract_matched() {
 
 606   local _args="$1" ;shift
 
 608   if [[ $_args =~ $_regex ]];then
 
 609     printf "%s" "${BASH_REMATCH[0]}"
 
 613 # Enable logging feature to the all the panes in the target window.
 
 614 xpns_enable_logging() {
 
 615   local _window_name="$1"     ; shift
 
 616   local _index_offset="$1"    ; shift
 
 617   local _log_dir="$1"         ; shift
 
 618   local _log_format="$1"      ; shift
 
 619   local _unprintable_str="$1" ; shift
 
 621   local _args_num=$(($# - 1))
 
 622   # Generate log files from arguments.
 
 624   while read -r _logfile ; do
 
 626     xpns_msg_debug "Start logging pipe-pane(cat >> '${_log_dir}/${_logfile}')"
 
 627     ${TMUX_XPANES_EXEC} \
 
 628       pipe-pane -t "${_window_name}.$(( _idx + _index_offset ))" \
 
 629       "cat >> '${_log_dir}/${_logfile}'" # Tilde expansion does not work here.
 
 632   for i in $(xpns_seq 0 "${_args_num}")
 
 634     # Replace empty string.
 
 635     printf "%s\\n" "${_args[i]:-${_unprintable_str}}"
 
 636   done | xpns_log_filenames "${_log_format}"
 
 640 ## Print "1" on the particular named pipe
 
 642   local _wait_id="$1" ; shift
 
 644   _fifo="${XP_CACHE_HOME}/__xpns_${_wait_id}"
 
 645   xpns_msg_debug "Notify to $_fifo"
 
 646   printf "%s\\n" 1 > "$_fifo" &
 
 649 xpns_notify_logging() {
 
 650   local _window_name="$1" ; shift
 
 651   local _args_num=$(($# - 1))
 
 652   for i in $(xpns_seq 0 "${_args_num}"); do
 
 653     xpns_notify "log_${_window_name}-${i}-$$"
 
 658   local _window_name="$1" ; shift
 
 659   local _args_num=$(($# - 1))
 
 660   for i in $(xpns_seq 0 "${_args_num}"); do
 
 661     xpns_notify "sync_${_window_name}-${i}-$$" &
 
 665 xpns_is_window_alive() {
 
 666   local _window_name="$1" ;shift
 
 667   local _speedy_await_flag="$1" ;shift
 
 668   local _def_allow_rename="$1" ;shift
 
 669   if ! ${TMUX_XPANES_EXEC} display-message -t "$_window_name" -p > /dev/null 2>&1 ;then
 
 670     xpns_msg_info "All the panes are closed before displaying the result."
 
 671     if [[ "${_speedy_await_flag}" -eq 0 ]] ;then
 
 672       xpns_msg_info "Use '-s' option instead of '-ss' option to avoid this behavior."
 
 674     xpns_restore_allow_rename "${_def_allow_rename-}"
 
 679 xpns_inject_title() {
 
 680   local _target_pane="$1" ;shift
 
 681   local _message="$1"     ;shift
 
 683   _pane_tty="$( ${TMUX_XPANES_EXEC} display-message -t "${_target_pane}" -p "#{pane_tty}" )"
 
 684   printf "\\033]2;%s\\033\\\\" "${_message}" > "${_pane_tty}"
 
 685   xpns_msg_debug "target_pane=${_target_pane} pane_title=${_message} pane_tty=${_pane_tty}"
 
 688 xpns_is_pane_title_required() {
 
 689   local _title_flag="$1"   ; shift
 
 690   local _extra_flag="$1"   ; shift
 
 691   local _pane_border_status=
 
 692   _pane_border_status=$(xpns_get_local_tmux_conf "pane-border-status")
 
 693   if [[ $_title_flag -eq 1 ]]; then
 
 695   elif [[ ${_extra_flag} -eq 1 ]] && \
 
 696        [[ "${_pane_border_status}" != "off" ]] && \
 
 697        [[ -n "${_pane_border_status}" ]] ;then
 
 699     # Even the -t option is not specified, it is required to inject pane title here.
 
 700     # Because user expects the title is displayed on the pane if the original window is
 
 701     # generated from tmux-xpanes with -t option.
 
 707 # Set pane titles for each pane for -t option
 
 709   local _window_name="$1"  ; shift
 
 710   local _index_offset="$1" ; shift
 
 715     _pane_index=$(( _index + _index_offset ))
 
 716     xpns_inject_title "${_window_name}.${_pane_index}" "${arg}"
 
 717     _index=$(( _index + 1 ))
 
 721 # Send command to the all the panes in the target window.
 
 722 xpns_send_commands() {
 
 723   local _window_name="$1"  ; shift
 
 724   local _index_offset="$1" ; shift
 
 725   local _repstr="$1"       ; shift
 
 726   local _cmd="$1"          ; shift
 
 732     _exec_cmd="${_cmd//${_repstr}/${arg}}"
 
 733     _pane_index=$(( _index + _index_offset ))
 
 734     ${TMUX_XPANES_EXEC} send-keys -t "${_window_name}.${_pane_index}" "${_exec_cmd}" C-m
 
 735     _index=$(( _index + 1 ))
 
 739 # Separate window vertically, when the number of panes is 1 or 2.
 
 740 xpns_organize_panes() {
 
 741   local _window_name="$1" ; shift
 
 746   if [[ "${_args_num}" -eq 1 ]]; then
 
 747     ${TMUX_XPANES_EXEC} select-layout -t "${_window_name}" even-horizontal
 
 748   elif [[ "${_args_num}" -gt 1 ]]; then
 
 749     ${TMUX_XPANES_EXEC} select-layout -t "${_window_name}" tiled
 
 754   if [[ "${XP_LAYOUT}" != "${XP_DEFAULT_LAYOUT}" ]]; then
 
 755     ${TMUX_XPANES_EXEC} select-layout -t "${_window_name}" "${XP_LAYOUT}"
 
 760 # Generate sequential number descending order.
 
 761 # seq is not used because old version of
 
 762 # seq does not generate descending oorder.
 
 772   eval "printf \"%d\\n\" {$_num1..$_num2}"
 
 777   local _fifo="${XP_CACHE_HOME}/__xpns_${_wait_id}"
 
 778   local _arr=("$_fifo")
 
 780   _fifo_arg=$(xpns_arr2args "${_arr[@]}")
 
 781   xpns_msg_debug "mkfifo $_fifo"
 
 783   xpns_msg_debug "grep -q 1 ${_fifo_arg}"
 
 784   printf "%s\\n" "grep -q 1 ${_fifo_arg}"
 
 787 # Split a new window into multiple panes.
 
 789 xpns_split_window() {
 
 790   local _window_name="$1"     ; shift
 
 791   local _log_flag="$1"        ; shift
 
 792   local _title_flag="$1"      ; shift
 
 793   local _speedy_flag="$1"     ; shift
 
 794   local _await_flag="$1"      ; shift
 
 795   local _pane_base_index="$1" ; shift
 
 796   local _repstr="$1"          ; shift
 
 797   local _cmd_template="$1"    ; shift
 
 801   _last_idx=$(( ${#args[@]} - 1 ))
 
 803   for i in $(xpns_seq $_last_idx 0)
 
 805     xpns_msg_debug "Index:${i} Argument:${args[i]}"
 
 806     _sep_count=$((_sep_count + 1))
 
 807     _exec_cmd="${_cmd_template//${_repstr}/${args[i]}}"
 
 810     if [[ $_speedy_flag -eq 1 ]]; then
 
 812       _exec_cmd=$(xpns_inject_wait_command "${_log_flag}" "${_title_flag}" "${_speedy_flag}" "${_await_flag}" "$i" "${_exec_cmd}")
 
 813       # Execute command as a child process of default-shell.
 
 814       ${TMUX_XPANES_EXEC} split-window -t "${_window_name}" -h -d "${_exec_cmd}"
 
 816       # Open login shell and execute command on the interactive screen.
 
 817       ${TMUX_XPANES_EXEC} split-window -t "${_window_name}" -h -d
 
 819     # Restraining that size of pane's width becomes
 
 820     # less than the minimum size which is defined by tmux.
 
 821     if [[ ${_sep_count} -gt 2 ]]; then
 
 822       ${TMUX_XPANES_EXEC} select-layout -t "${_window_name}" tiled
 
 828 # Create new panes to the  existing window.
 
 830 #    func <window name> <offset of index> <number of pane>
 
 832 xpns_prepare_extra_panes() {
 
 833   local _window_name="$1"     ; shift
 
 834   local _pane_base_index="$1" ; shift
 
 835   local _log_flag="$1"        ; shift
 
 836   local _title_flag="$1"      ; shift
 
 837   local _speedy_flg="$1"      ; shift
 
 838   local _await_flg="$1"       ; shift
 
 839   # specify a pane which has the biggest index number.
 
 840   #   Because pane_id may not be immutable.
 
 841   #   If the small number of index is specified here, correspondance between pane_title and command can be slip off.
 
 842   ${TMUX_XPANES_EXEC} select-pane -t "${_window_name}.${_pane_base_index}"
 
 844   # split window into multiple panes
 
 851     "${_pane_base_index}" \
 
 855 xpns_get_joined_begin_commands () {
 
 857   if [[ "${#XP_BEGIN_ARGS[*]}" -lt 1 ]]; then
 
 858     printf "%s" "${_commands}"
 
 861   printf "%s\\n" "${XP_BEGIN_ARGS[@]}" "${_commands}"
 
 864 xpns_inject_wait_command () {
 
 865   local _log_flag="$1"    ; shift
 
 866   local _title_flag="$1"  ; shift
 
 867   local _speedy_flg="$1"  ; shift
 
 868   local _await_flg="$1"   ; shift
 
 869   local _idx="$1"         ; shift
 
 870   local _exec_cmd="$1"    ; shift
 
 872   ## Speedy mode + logging
 
 873   if [[ "${_log_flag}" -eq 1 ]] && [[ "${_speedy_flg}" -eq 1 ]]; then
 
 874     # Wait for start of logging
 
 875     # Without this part, logging thread may start after new process is finished.
 
 876     # Execute function to wait for logging start.
 
 877     _exec_cmd="$(xpns_wait_func "log_${_window_name}-${_idx}-$$")"$'\n'"${_exec_cmd}"
 
 880   ## Speedy mode (Do not allow to close panes before the separation is finished).
 
 881   if [[ "${_speedy_flg}" -eq 1 ]]; then
 
 882     _exec_cmd="$(xpns_wait_func "sync_${_window_name}-${_idx}-$$")"$'\n'${_exec_cmd}
 
 885   ## -s: Speedy mode (Not -ss: Speedy mode + nowait)
 
 886   if [[ "${_await_flg}" -eq 1 ]]; then
 
 888     _msg="$(xpns_arr2args "${TMUX_XPANES_PANE_DEAD_MESSAGE}" | sed 's/"/\\"/g')"
 
 889     _exec_cmd="${_exec_cmd}"$'\n'"${XP_SHELL} -c \"printf -- ${_msg} >&2 && read\""
 
 891   printf "%s" "${_exec_cmd}"
 
 895   local _window_name="$1" ; shift
 
 896   local _attach_flg="$1"  ; shift
 
 897   local _speedy_flg="$1"  ; shift
 
 898   local _exec_cmd="$1"    ; shift
 
 902   if [[ "${_attach_flg}" -eq 1 ]]; then
 
 903     if [[ "${_speedy_flg}" -eq 1 ]]; then
 
 904       _window_id=$(${TMUX_XPANES_EXEC} new-window -n "${_window_name}" -F '#{window_id}' -P "${_exec_cmd}")
 
 906       _window_id=$(${TMUX_XPANES_EXEC} new-window -n "${_window_name}" -F '#{window_id}' -P )
 
 910     if [[ "${_speedy_flg}" -eq 1 ]]; then
 
 911       _window_id=$(${TMUX_XPANES_EXEC} new-window -n "${_window_name}" -F '#{window_id}' -P -d "${_exec_cmd}")
 
 913       _window_id=$(${TMUX_XPANES_EXEC} new-window -n "${_window_name}" -F '#{window_id}' -P -d)
 
 916   printf "%s" "${_window_id}"
 
 919 xpns_new_pane_vertical () {
 
 920   local _window_id="$1"   ; shift
 
 921   local _cell_height="$1" ; shift
 
 922   local _speedy_flg="$1"  ; shift
 
 923   local _exec_cmd="$1"    ; shift
 
 925   if [[ "${_speedy_flg}" -eq 1 ]]; then
 
 926     _pane_id="$(${TMUX_XPANES_EXEC} split-window -t "$_window_id" -v -d -l "${_cell_height}" -F '#{pane_id}' -P "${_exec_cmd}")"
 
 928     _pane_id="$(${TMUX_XPANES_EXEC} split-window -t "$_window_id" -v -d -l "${_cell_height}" -F '#{pane_id}' -P)"
 
 930   printf "%s\\n" "${_pane_id}"
 
 933 xpns_split_pane_horizontal () {
 
 934   local _target_pane_id="$1" ; shift
 
 935   local _cell_width="$1"     ; shift
 
 936   local _speedy_flg="$1"     ; shift
 
 937   local _exec_cmd="$1"       ; shift
 
 938   if [[ "${_speedy_flg}" -eq 1 ]]; then
 
 939     ${TMUX_XPANES_EXEC} split-window -t "$_target_pane_id" -h -d -l "$_cell_width" "${_exec_cmd}"
 
 941     ${TMUX_XPANES_EXEC} split-window -t "$_target_pane_id" -h -d -l "$_cell_width"
 
 945 xpns_prepare_window () {
 
 946   local _window_name="$1"     ; shift
 
 947   local _log_flag="$1"        ; shift
 
 948   local _title_flag="$1"      ; shift
 
 949   local _attach_flg="$1"      ; shift
 
 950   local _speedy_flg="$1"      ; shift
 
 951   local _await_flg="$1"       ; shift
 
 952   local _repstr="$1"          ; shift
 
 953   local _cmd_template="$1"    ; shift
 
 955   local _window_height="$XP_WINDOW_HEIGHT"
 
 956   local _window_width="$XP_WINDOW_WIDTH"
 
 957   local _col="$XP_OPT_CUSTOM_SIZE_COLS"
 
 958   local _row="$XP_OPT_CUSTOM_SIZE_ROWS"
 
 959   local _cols=("${XP_COLS[@]}")
 
 960   local _cols_offset=("${XP_COLS_OFFSETS[@]}")
 
 963   local _first_pane_id=
 
 967   local _top_pane_height=
 
 968   local _current_pane_width=
 
 975   _cell_height=$(( ( _window_height - _row + 1 ) / _row ))
 
 976   ## Insert first element
 
 977   _exec_cmd="${_cmd_template//${_repstr}/${_args[0]}}"
 
 978   _exec_cmd="$(xpns_inject_wait_command "${_log_flag}" "${_title_flag}" "${_speedy_flg}" "${_await_flg}" 0 "${_exec_cmd}")"
 
 979   _window_id=$(xpns_new_window "${_window_name}" "${_attach_flg}" "${_speedy_flg}" "${_exec_cmd}")
 
 980   _first_pane_id=$(${TMUX_XPANES_EXEC} display-message -t "$_window_id" -p -F '#{pane_id}' | head -n 1)
 
 982   ## Start from last row
 
 983   for (( i = _row - 1 ; i > 0 ; i-- ));do
 
 985     _cell_width=$(( ( _window_width - _col + 1 ) / _col ))
 
 986     xpns_msg_debug "_col=$_col"
 
 987     (( _offset = _cols_offset[i] ))
 
 988     for (( j = 0 ; j < _col ; j++ ));do
 
 989       if (( j == 0 )) ;then
 
 990         (( idx = _offset - _col ))
 
 992         # Insert first element of the row first
 
 993         _exec_cmd="${_cmd_template//${_repstr}/${_args[idx]}}"
 
 994         _exec_cmd="$(xpns_inject_wait_command "${_log_flag}" "${_title_flag}" "${_speedy_flg}" "${_await_flg}" "${idx}" "${_exec_cmd}")"
 
 995         _pane_id=$(xpns_new_pane_vertical "${_window_name}" "${_cell_height}" "${_speedy_flg}" "${_exec_cmd}")
 
 997       # Separate row into columns
 
 998       if (( j != 0 )) ;then
 
 999         (( idx = _offset - j ))
 
1000         _exec_cmd="${_cmd_template//${_repstr}/${_args[idx]}}"
 
1001         _exec_cmd="$(xpns_inject_wait_command "${_log_flag}" "${_title_flag}" "${_speedy_flg}" "${_await_flg}" "${idx}" "${_exec_cmd}")"
 
1002         ## Separate row into columns
 
1003         _current_pane_width=$(${TMUX_XPANES_EXEC} display-message -t "$_pane_id" -p '#{pane_width}' | head -n 1)
 
1004         _rest_col=$(( _col - j + 1 ))
 
1005         _cell_width=$(( ( _current_pane_width - _rest_col + 1 ) / _rest_col ))
 
1006         xpns_split_pane_horizontal "$_pane_id" "$_cell_width" "${_speedy_flg}" "${_exec_cmd}"
 
1011     _top_pane_height=$(${TMUX_XPANES_EXEC} display-message -t "$_window_id" -p '#{pane_height}' | head -n 1)
 
1013     xpns_msg_debug "_top_pane_height=$_top_pane_height _rest_row=$_rest_row"
 
1014     _cell_height=$(( ( _top_pane_height - _rest_row + 1 ) / _rest_row ))
 
1017   # Split first row into columns
 
1019   _cell_width=$(( ( _window_width - _col + 1 ) / _col ))
 
1020   for (( j = 1 ; j < _col ; j++ ));do
 
1021     idx=$(( _cols_offset[0] - j ))
 
1023     _current_pane_width=$(${TMUX_XPANES_EXEC} display-message -t "$_first_pane_id" -p '#{pane_width}' | head -n 1)
 
1024     _rest_col=$(( _col - j + 1 ))
 
1025     _cell_width=$(( ( _current_pane_width - _rest_col + 1 ) / _rest_col ))
 
1026     ## Split top row into columns
 
1027     _exec_cmd="${_cmd_template//${_repstr}/${_args[idx]}}"
 
1028     _exec_cmd="$(xpns_inject_wait_command "${_log_flag}" "${_title_flag}" "${_speedy_flg}" "${_await_flg}" "${idx}" "${_exec_cmd}")"
 
1029     xpns_split_pane_horizontal "${_first_pane_id}" "${_cell_width}" "${_speedy_flg}" "${_exec_cmd}"
 
1033 xpns_is_session_running() {
 
1035   ${TMUX_XPANES_EXEC} -S "${_socket}" list-session > /dev/null 2>&1
 
1038 # Remove unnecessary session files as much as possible
 
1039 # to let xpanes avoids to load old .tmux.conf.
 
1040 xpns_clean_session() {
 
1041   if [[ "${XP_SOCKET_PATH}" != "${XP_DEFAULT_SOCKET_PATH}" ]]; then
 
1044   # Delete old socket file (xpanes v3.1.0 or before).
 
1045   if [[ -e "${XP_DEFAULT_SOCKET_PATH_BASE}" ]]; then
 
1046     if ! xpns_is_session_running "${XP_DEFAULT_SOCKET_PATH_BASE}" ;then
 
1047       xpns_msg_debug "socket(${XP_DEFAULT_SOCKET_PATH_BASE}) is not running. Remove it"
 
1048       rm -f "${XP_DEFAULT_SOCKET_PATH_BASE}"
 
1051   for _socket in "${XP_CACHE_HOME}"/socket.* ;do
 
1052     xpns_msg_debug "file = ${_socket}"
 
1053     if ! xpns_is_session_running "${_socket}" ;then
 
1054       xpns_msg_debug "socket(${_socket}) is not running. Remove it"
 
1057       xpns_msg_debug "socket(${_socket}) is running. Keep ${_socket}"
 
1063 # Split a new window which was created by tmux into multiple panes.
 
1065 #    xpns_prepare_preset_layout_window <window name> <offset of index> <number of pane> <attach or not>
 
1067 xpns_prepare_preset_layout_window() {
 
1068   local _window_name="$1"     ; shift
 
1069   local _pane_base_index="$1" ; shift
 
1070   local _log_flag="$1"        ; shift
 
1071   local _title_flag="$1"      ; shift
 
1072   local _attach_flg="$1"      ; shift
 
1073   local _speedy_flg="$1"      ; shift
 
1074   local _await_flg="$1"       ; shift
 
1075   # Create new window.
 
1076   if [[ "${_attach_flg}" -eq 1 ]]; then
 
1077     ${TMUX_XPANES_EXEC} new-window -n "${_window_name}"
 
1080     ${TMUX_XPANES_EXEC} new-window -n "${_window_name}" -d
 
1083   # specify a pane which has the youngest number of index.
 
1084   ${TMUX_XPANES_EXEC} select-pane -t "${_window_name}.${_pane_base_index}"
 
1086   # split window into multiple panes
 
1093     "${_pane_base_index}" \
 
1096   ### If the first pane is still remaining,
 
1097   ### panes cannot be organized well.
 
1098   # Delete the first pane
 
1099   ${TMUX_XPANES_EXEC} kill-pane -t "${_window_name}.${_pane_base_index}"
 
1101   # Select second pane here.
 
1102   #   If the command gets error, it would most likely be caused by user (XP_ENOPANE).
 
1103   #   Suppress error message here and announce it in xpns_execution.
 
1104   ${TMUX_XPANES_EXEC} select-pane -t "${_window_name}.${_pane_base_index}" > /dev/null 2>&1
 
1107 # Check whether given command is in the PATH or not.
 
1110   while read -r cmd ; do
 
1111     if ! type "${cmd}" > /dev/null 2>&1; then
 
1112       if [[ "${cmd}" == "tmux" ]] && [[ "${TMUX_XPANES_EXEC}" == "tmux" ]]; then
 
1113         xpns_msg_error "${cmd} is required. Install ${cmd} or set TMUX_XPANES_EXEC variable."
 
1115       elif [[ "${cmd}" != "tmux" ]]; then
 
1116         xpns_msg_error "${cmd} is required."
 
1120   done < <(echo "${_cmds}" | tr ' ' '\n')
 
1122   if ! mkdir -p "${XP_CACHE_HOME}";then
 
1123     xpns_msg_warning "failed to create cache directory '${XP_CACHE_HOME}'."
 
1126   # Do not omit this part, this is used by testing.
 
1127   TMUX_XPANES_TMUX_VERSION="${TMUX_XPANES_TMUX_VERSION:-$(xpns_get_tmux_version)}"
 
1128   if ( xpns_tmux_is_greater_equals \
 
1129     "${XP_SUPPORT_TMUX_VERSION_LOWER}" \
 
1130     "${TMUX_XPANES_TMUX_VERSION}" ) ;then
 
1131     : "Supported tmux version"
 
1134 "'${XP_THIS_FILE_NAME}' may not work correctly! Please check followings.
 
1135 * tmux is installed correctly.
 
1136 * Supported tmux version is installed.
 
1137   Version ${XP_SUPPORT_TMUX_VERSION_LOWER} and over is officially supported."
 
1143 xpns_pipe_filter() {
 
1144   local _number="${1-}"
 
1145   if [[ -z "${_number-}" ]]; then
 
1148     xargs -n "${_number}"
 
1152 xpns_set_args_per_pane() {
 
1153   local _pane_num="$1"; shift
 
1154   local _filtered_args=()
 
1155   while read -r _line; do
 
1156     _filtered_args+=("${_line}")
 
1157   done < <(xargs -n "${_pane_num}" <<<"$(xpns_arr2args "${XP_ARGS[@]}")")
 
1158   XP_ARGS=("${_filtered_args[@]}")
 
1161 xpns_get_window_height_width() {
 
1166   local _pattern='^([0-9]+)[ \t]+([0-9]+)$'
 
1168   if ! type stty > /dev/null 2>&1; then
 
1169     xpns_msg_debug "'stty' does not exist: Failed to get window height and size. Skip checking"
 
1173   ## This condition is used for unit testing
 
1174   if [[ -z "${XP_IS_PIPE_MODE-}" ]]; then
 
1175     if [[ ! -t 0 ]]; then
 
1179   if [[ $XP_IS_PIPE_MODE -eq 0 ]]; then
 
1180     if _result=$(stty size 2> /dev/null) && [[ "$_result" =~ $_pattern ]];then
 
1181       _height="${BASH_REMATCH[1]}"
 
1182       _width="${BASH_REMATCH[2]}"
 
1183       xpns_msg_debug "window height: $_height, width: $_width"
 
1184       printf "%s\\n" "$_height $_width"
 
1188     if ! type ps > /dev/null 2>&1 ;then
 
1189       xpns_msg_debug "'ps' does not exist: Failed to get window height and size. Skip checking"
 
1192     { read -r; read -r _dev; } < <(ps -o tty -p $$)
 
1193     ## If it's Linux, -F option is used
 
1194     if _result=$(stty -F "/dev/${_dev}" size 2> /dev/null) && [[ "$_result" =~ $_pattern ]];then
 
1195       _height="${BASH_REMATCH[1]}"
 
1196       _width="${BASH_REMATCH[2]}"
 
1197       xpns_msg_debug "window height: $_height, width: $_width"
 
1198       printf "%s\\n" "$_height $_width"
 
1201     ## If it's BSD, macOS, -F option is used
 
1202     if _result=$(stty -f "/dev/${_dev}" size 2> /dev/null) && [[ "$_result" =~ $_pattern ]];then
 
1203       _height="${BASH_REMATCH[1]}"
 
1204       _width="${BASH_REMATCH[2]}"
 
1205       xpns_msg_debug "window height: $_height, width: $_width"
 
1206       printf "%s\\n" "$_height $_width"
 
1214 xpns_check_cell_size_bulk() {
 
1215   local _cell_num="$1"    ; shift
 
1216   local _bulk_cols="$1"   ; shift
 
1217   local _win_height="$1"  ; shift
 
1218   local _win_width="$1"   ; shift
 
1219   local _ignore_flag="$1" ; shift
 
1221   # shellcheck disable=SC2178
 
1225   IFS="," read -r -a _all_cols <<< "${_bulk_cols}"
 
1226   _rows="${#_all_cols[@]}"
 
1227   for i in "${_all_cols[@]}"; do
 
1228     (( i >= _cols )) && (( _cols = i ))
 
1229     (( _sum_cell = _sum_cell + i ))
 
1231   if (( _sum_cell != _cell_num )) ;then
 
1232     xpns_msg_error "Number of cols does not equals to the number of arguments."
 
1233     xpns_msg_error "Expected (# of args) : $_cell_num, Actual (--bulk-cols) : $_sum_cell)."
 
1234     return ${XP_ELAYOUT:-6}
 
1236   local cell_height=$(( ( _win_height - _rows + 1 ) / _rows ))
 
1237   local cell_width=$(( ( _win_width - _cols + 1 ) / _cols ))
 
1239   ## Display basic information
 
1240   xpns_msg_debug "Window: { Height: $_win_height, Width: $_win_width }"
 
1241   xpns_msg_debug "Cell: { Height: $cell_height, Width: $cell_width }"
 
1242   xpns_msg_debug "# Of Panes: ${_cell_num}"
 
1243   xpns_msg_debug "         | Row[0] --...--> Row[MAX]"
 
1244   xpns_msg_debug "    -----+------------------------..."
 
1245   xpns_msg_debug "    Col[]| ${_all_cols[*]}"
 
1246   xpns_msg_debug "    -----+------------------------..."
 
1248   if [[ "$_ignore_flag" -ne 1 ]] && ( (( cell_height < 2 )) || (( cell_width < 2 )) ); then
 
1249     xpns_msg_error "Expected pane size is too small (height: $cell_height lines, width: $cell_width chars)"
 
1250     return ${XP_ESMLPANE:-7}
 
1252   printf "%s\\n" "${_cols} ${_rows} ${_all_cols[*]}"
 
1255 xpns_check_cell_size() {
 
1256   local _cell_num="$1"    ; shift
 
1257   local _cols="$1"        ; shift
 
1258   local _rows="$1"        ; shift
 
1259   local _win_height="$1"  ; shift
 
1260   local _win_width="$1"   ; shift
 
1261   local _ignore_flag="$1" ; shift
 
1262   local _all_cols_num=
 
1265   if [[ -n "${_cols-}" ]] && [[ -n "${_rows-}" ]];then
 
1266     xpns_msg_warning "Both col size and row size are provided. Col size is preferentially going to be applied."
 
1268   ## if col is only defined
 
1269   if [[ -n "${_cols-}" ]] ;then
 
1270     read -r _cols _rows < <(xpns_adjust_col_row "${_cols-}" 0 "${_cell_num}")
 
1271     IFS=" " read -r -a _all_rows <<< "$(xpns_divide_equally "${_cell_num}" "${_cols}")"
 
1272     _all_cols_num="$(xpns_nums_transpose "${_all_rows[@]}")"
 
1274   ## if row is only defined
 
1275   elif [[ -n "${_rows-}" ]] ;then
 
1276     read -r _cols _rows < <(xpns_adjust_col_row 0 "${_rows-}" "${_cell_num}")
 
1277     _all_cols_num="$(xpns_divide_equally "${_cell_num}" "${_rows}")"
 
1279   ## if both are undefined
 
1281     read -r _cols _rows < <(xpns_adjust_col_row 0 0 "${_cell_num}")
 
1282     _all_cols_num="$(xpns_divide_equally "${_cell_num}" "${_rows}")"
 
1285   local cell_height=$(( ( _win_height - _rows + 1 ) / _rows ))
 
1286   local cell_width=$(( ( _win_width - _cols + 1 ) / _cols ))
 
1288   ## Display basic information
 
1289   xpns_msg_debug "Window: { Height: $_win_height, Width: $_win_width }"
 
1290   xpns_msg_debug "Cell: { Height: $cell_height, Width: $cell_width }"
 
1291   xpns_msg_debug "# Of Panes: ${_cell_num}"
 
1292   xpns_msg_debug "         | Row[0] --...--> Row[MAX]"
 
1293   xpns_msg_debug "    -----+------------------------..."
 
1294   xpns_msg_debug "    Col[]| ${_all_cols_num}"
 
1295   xpns_msg_debug "    -----+------------------------..."
 
1297   if [[ "$_ignore_flag" -ne 1 ]] && ( (( cell_height < 2 )) || (( cell_width < 2 )) ); then
 
1298     xpns_msg_error "Expected pane size is too small (height: $cell_height lines, width: $cell_width chars)"
 
1299     return "${XP_ESMLPANE:-7}"
 
1301   printf "%s\\n" "${_cols} ${_rows} ${_all_cols_num}"
 
1304 # Execute from Normal mode1
 
1305 xpns_pre_execution() {
 
1309   if [[ ${XP_OPT_EXTRA} -eq 1 ]];then
 
1310     xpns_msg_error "'-x' must be used within the running tmux session."
 
1314   # Run as best effort.
 
1315   # Because after the tmux session is created, cols and rows would be provided by tmux.
 
1316   IFS=" " read -r XP_WINDOW_HEIGHT XP_WINDOW_WIDTH < <(xpns_get_window_height_width) && {
 
1317     local _arg_num="${#XP_ARGS[@]}"
 
1318     local _cell_num _tmp_col_row_cols _tmp_cols
 
1319     if [[ -n "$XP_MAX_PANE_ARGS" ]] && (( XP_MAX_PANE_ARGS > 1 ));then
 
1320       _cell_num=$(( _arg_num / XP_MAX_PANE_ARGS ))
 
1322       _cell_num="${_arg_num}"
 
1324     if [[ -n "${XP_OPT_BULK_COLS}" ]]; then
 
1325       _tmp_col_row_cols="$(xpns_check_cell_size_bulk \
 
1327         "${XP_OPT_BULK_COLS}" \
 
1328         "${XP_WINDOW_HEIGHT}" \
 
1329         "${XP_WINDOW_WIDTH}" \
 
1330         "${XP_OPT_IGNORE_SIZE_LIMIT:-0}")"
 
1331       local _exit_status="$?"
 
1332       [[ $_exit_status -eq ${XP_ELAYOUT} ]] && exit ${XP_ELAYOUT}
 
1333       [[ $_exit_status -eq ${XP_ESMLPANE} ]] && exit ${XP_ESMLPANE}
 
1335       _tmp_col_row_cols="$(xpns_check_cell_size \
 
1337         "${XP_OPT_CUSTOM_SIZE_COLS-}" \
 
1338         "${XP_OPT_CUSTOM_SIZE_ROWS-}" \
 
1339         "${XP_WINDOW_HEIGHT}" \
 
1340         "${XP_WINDOW_WIDTH}" \
 
1341         "${XP_OPT_IGNORE_SIZE_LIMIT:-0}")"
 
1342       [[ $? -eq ${XP_ESMLPANE} ]] && exit ${XP_ESMLPANE}
 
1345     IFS=" " read -r XP_OPT_CUSTOM_SIZE_COLS XP_OPT_CUSTOM_SIZE_ROWS _tmp_cols <<< "$_tmp_col_row_cols"
 
1346     IFS=" " read -r -a XP_COLS <<< "${_tmp_cols}"
 
1347     IFS=" " read -r -a XP_COLS_OFFSETS <<< "$(printf "%s\\n" "${XP_COLS[*]}" | xpns_nums_accumulate_sum)"
 
1348     xpns_msg_debug "Options: $(xpns_arr2args "${XP_OPTIONS[@]}")"
 
1349     xpns_msg_debug "Arguments: $(xpns_arr2args "${XP_ARGS[@]}")"
 
1353   # Because any arguments may have `-`
 
1354   if [[ ${XP_NO_OPT} -eq 1 ]]; then
 
1355     XP_ARGS=("--" "${XP_ARGS[@]}")
 
1358   # If there is any options, escape them.
 
1359   if [[ -n "${XP_OPTIONS[*]-}" ]]; then
 
1360     _opts4args=$(xpns_arr2args "${XP_OPTIONS[@]}")
 
1362   _args4args=$(xpns_arr2args "${XP_ARGS[@]}")
 
1364   # Run as best effort
 
1365   xpns_clean_session || true
 
1367   # Create new session.
 
1368   ${TMUX_XPANES_EXEC} -S "${XP_SOCKET_PATH}" new-session \
 
1369     -s "${XP_SESSION_NAME}" \
 
1370     -n "${XP_TMP_WIN_NAME}" \
 
1371     -d "${XP_ABS_THIS_FILE_NAME} ${_opts4args} ${_args4args}"
 
1373   # Avoid attaching (for unit testing).
 
1374   if [[ ${XP_OPT_ATTACH} -eq 1 ]]; then
 
1375     if ! ${TMUX_XPANES_EXEC} -S "${XP_SOCKET_PATH}" attach-session -t "${XP_SESSION_NAME}" \
 
1376       && [[ ${XP_IS_PIPE_MODE} -eq 1 ]]; then
 
1377       ## In recovery case, overwrite trap to keep socket file
 
1378       trap 'rm -f "${XP_CACHE_HOME}"/__xpns_*$$;' EXIT
 
1380       xpns_msg "Recovery" \
 
1381 "Execute below command line to re-attach the new session.
 
1383 ${TMUX_XPANES_EXEC} -S ${XP_SOCKET_PATH} attach-session -t ${XP_SESSION_NAME}
 
1391 # Execute from inside of tmux session
 
1393   local _pane_base_index=
 
1395   local _last_args_idx=
 
1396   local _def_allow_rename=
 
1399   if [[ ${XP_IS_PIPE_MODE} -eq 0 ]] && [[ -n "${XP_MAX_PANE_ARGS-}" ]];then
 
1400     xpns_set_args_per_pane "${XP_MAX_PANE_ARGS}"
 
1403   ## Fix window size and define pane size
 
1405     local  _tmp_col_row_cols _tmp_cols
 
1406     IFS=" " read -r XP_WINDOW_HEIGHT XP_WINDOW_WIDTH < \
 
1407       <(${TMUX_XPANES_EXEC} display-message -p '#{window_height} #{window_width}')
 
1408     if [[ -n "${XP_OPT_BULK_COLS}" ]]; then
 
1409       _tmp_col_row_cols="$(xpns_check_cell_size_bulk \
 
1411         "${XP_OPT_BULK_COLS}" \
 
1412         "${XP_WINDOW_HEIGHT}" \
 
1413         "${XP_WINDOW_WIDTH}" \
 
1414         "${XP_OPT_IGNORE_SIZE_LIMIT:-0}")"
 
1415       local _exit_status="$?"
 
1416       [[ $_exit_status -eq ${XP_ELAYOUT} ]] && exit ${XP_ELAYOUT}
 
1417       [[ $_exit_status -eq ${XP_ESMLPANE} ]] && exit ${XP_ESMLPANE}
 
1419       _tmp_col_row_cols="$(xpns_check_cell_size \
 
1421         "${XP_OPT_CUSTOM_SIZE_COLS-}" \
 
1422         "${XP_OPT_CUSTOM_SIZE_ROWS-}" \
 
1423         "${XP_WINDOW_HEIGHT}" \
 
1424         "${XP_WINDOW_WIDTH}" \
 
1425         "${XP_OPT_IGNORE_SIZE_LIMIT:-0}")"
 
1426       [[ $? -eq ${XP_ESMLPANE} ]] && exit ${XP_ESMLPANE}
 
1428     IFS=" " read -r XP_OPT_CUSTOM_SIZE_COLS XP_OPT_CUSTOM_SIZE_ROWS _tmp_cols <<< "$_tmp_col_row_cols"
 
1429     IFS=" " read -r -a XP_COLS <<< "${_tmp_cols}"
 
1430     IFS=" " read -r -a XP_COLS_OFFSETS <<< "$(printf "%s\\n" "${XP_COLS[*]}" | xpns_nums_accumulate_sum)"
 
1431     xpns_msg_debug "Options: $(xpns_arr2args "${XP_OPTIONS[@]}")"
 
1432     xpns_msg_debug "Arguments: $(xpns_arr2args "${XP_ARGS[@]}")"
 
1435   _pane_base_index=$(xpns_get_global_tmux_conf 'pane-base-index')
 
1436   _last_args_idx=$((${#XP_ARGS[@]} - 1))
 
1437   _def_allow_rename="$(xpns_get_global_tmux_conf 'allow-rename')"
 
1439   xpns_suppress_allow_rename "${_def_allow_rename-}"
 
1440   XP_CMD_UTILITY="$(xpns_get_joined_begin_commands "${XP_CMD_UTILITY}")"
 
1442   if [[ ${XP_OPT_EXTRA} -eq 1 ]];then
 
1443     # Reuse existing window name
 
1444     # tmux 1.6 does not support -F option
 
1445     _window_name="$( ${TMUX_XPANES_EXEC} display -p -F "#{window_id}" )"
 
1446     _pane_count="$( ${TMUX_XPANES_EXEC} list-panes | grep -c . )"
 
1447     _pane_base_index=$(( _pane_base_index + _pane_count - 1 ))
 
1448     _pane_active_pane_id=$(${TMUX_XPANES_EXEC} display -p -F "#{pane_id}")
 
1451       xpns_generate_window_name \
 
1457   ## --------------------
 
1458   # Prepare window and panes
 
1459   ## --------------------
 
1460   if [[ ${XP_OPT_EXTRA} -eq 1 ]];then
 
1461     xpns_prepare_extra_panes \
 
1463       "${_pane_base_index}" \
 
1464       "${XP_OPT_LOG_STORE}" \
 
1465       "${XP_OPT_SET_TITLE}" \
 
1466       "${XP_OPT_SPEEDY}" \
 
1467       "${XP_OPT_SPEEDY_AWAIT}" \
 
1469       "${XP_CMD_UTILITY}" \
 
1471   elif [[ ${XP_OPT_USE_PRESET_LAYOUT} -eq 1 ]];then
 
1472     xpns_prepare_preset_layout_window \
 
1474       "${_pane_base_index}" \
 
1475       "${XP_OPT_LOG_STORE}" \
 
1476       "${XP_OPT_SET_TITLE}" \
 
1477       "${XP_OPT_ATTACH}" \
 
1478       "${XP_OPT_SPEEDY}" \
 
1479       "${XP_OPT_SPEEDY_AWAIT}" \
 
1481       "${XP_CMD_UTILITY}" \
 
1483   elif [[ ${XP_OPT_USE_PRESET_LAYOUT} -eq 0 ]];then
 
1484     xpns_prepare_window \
 
1486       "${XP_OPT_LOG_STORE}" \
 
1487       "${XP_OPT_SET_TITLE}" \
 
1488       "${XP_OPT_ATTACH}" \
 
1489       "${XP_OPT_SPEEDY}" \
 
1490       "${XP_OPT_SPEEDY_AWAIT}" \
 
1492       "${XP_CMD_UTILITY}" \
 
1496   ## With -ss option, it is possible to close all the panes as of here.
 
1497   ## Check status of the window. If no window exists, there is nothing to do execpt to exit.
 
1498   xpns_msg_debug "xpns_is_window_alive:1: After window separation"
 
1499   xpns_is_window_alive "${_window_name}" "${XP_OPT_SPEEDY_AWAIT}" "${_def_allow_rename-}"
 
1501   if [[ ${XP_OPT_EXTRA} -eq 1 ]];then
 
1502     # Set offset to avoid sending command to the original pane.
 
1503     _pane_base_index=$((_pane_base_index + 1))
 
1504     # Avoid to make layout even-horizontal even if there are many panes.
 
1505     # in xpns_organize_panes
 
1506     _last_args_idx=$((_last_args_idx + _pane_count))
 
1507     # Re-select the windown that was active before.
 
1508     ${TMUX_XPANES_EXEC} select-pane -t "${_window_name}.${_pane_active_pane_id}"
 
1511   if [[ ${XP_OPT_LOG_STORE} -eq 1 ]]; then
 
1512     xpns_enable_logging \
 
1514       "${_pane_base_index}" \
 
1515       "${TMUX_XPANES_LOG_DIRECTORY}" \
 
1516       "${TMUX_XPANES_LOG_FORMAT}" \
 
1520     if [[ $XP_OPT_SPEEDY -eq 1 ]]; then
 
1521       xpns_notify_logging \
 
1527   xpns_msg_debug "xpns_is_window_alive:2: After logging"
 
1528   xpns_is_window_alive "${_window_name}" "${XP_OPT_SPEEDY_AWAIT}" "${_def_allow_rename-}"
 
1530   # Set pane titles for each pane.
 
1531   if xpns_is_pane_title_required "${XP_OPT_SET_TITLE}" "${XP_OPT_EXTRA}" ;then
 
1534       "${_pane_base_index}" \
 
1538   if [[ $XP_OPT_SPEEDY -eq 1 ]];then
 
1544   xpns_msg_debug "xpns_is_window_alive:3: After setting title"
 
1545   xpns_is_window_alive "${_window_name}" "${XP_OPT_SPEEDY_AWAIT}" "${_def_allow_rename-}"
 
1547   # Sending operations for each pane.
 
1548   # With -s option, command is already sent.
 
1549   if [[ $XP_OPT_SPEEDY -eq 0 ]]; then
 
1550     xpns_send_commands \
 
1552       "${_pane_base_index}" \
 
1554       "${XP_CMD_UTILITY}" \
 
1558   xpns_msg_debug "xpns_is_window_alive:4: After sending commands"
 
1559   xpns_is_window_alive "${_window_name}" "${XP_OPT_SPEEDY_AWAIT}" "${_def_allow_rename-}"
 
1561   ## With -l <layout>, panes are organized.
 
1562   ## As well as -x, they are re-organized.
 
1563   if [[ $XP_OPT_USE_PRESET_LAYOUT -eq 1 ]] || [[ ${XP_OPT_EXTRA} -eq 1 ]]; then
 
1564     xpns_organize_panes \
 
1569   # Enable broadcasting
 
1570   if [[ ${XP_OPT_IS_SYNC} -eq 1 ]] && [[ ${XP_OPT_EXTRA} -eq 0 ]]; then
 
1571     ${TMUX_XPANES_EXEC} \
 
1572       set-window-option -t "${_window_name}" \
 
1573       synchronize-panes on
 
1576   ## In case of -t option
 
1577   if [[ ${XP_OPT_SET_TITLE} -eq 1 ]] && [[ ${XP_OPT_CHANGE_BORDER} -eq 1 ]]; then
 
1579     ${TMUX_XPANES_EXEC} \
 
1580       set-window-option -t "${_window_name}" \
 
1581       pane-border-format "${TMUX_XPANES_PANE_BORDER_FORMAT}"
 
1582     # Show border status
 
1583     ${TMUX_XPANES_EXEC} \
 
1584       set-window-option -t "${_window_name}" \
 
1585       pane-border-status "${TMUX_XPANES_PANE_BORDER_STATUS}"
 
1588   # In case of -x, this statement is skipped to keep the original window name
 
1589   if [[ ${XP_OPT_EXTRA} -eq 0 ]];then
 
1590     # Restore original window name.
 
1591     ${TMUX_XPANES_EXEC} \
 
1592       rename-window -t "${_window_name}" \
 
1593       -- "$(printf "%s\\n" "${_window_name}" | xpns_key2value)"
 
1596   xpns_restore_allow_rename "${_def_allow_rename-}"
 
1600 # Arrange options for pipe mode
 
1601 #  * argument -> command
 
1602 #  * stdin -> argument
 
1604 xpns_switch_pipe_mode() {
 
1605   local _pane_num4new_term=""
 
1606   if [[ -n "${XP_ARGS[*]-}" ]] && [[ -n "${XP_CMD_UTILITY-}" ]]; then
 
1607     xpns_msg_error "Both arguments and other options (like '-c', '-e') which updates <command> are given."
 
1611   if [[ -z "${TMUX-}" ]]; then
 
1612     xpns_msg_warning "Attached session is required for 'Pipe mode'."
 
1613     # This condition is used when the following situations.
 
1614     #   * Enter from outside of tmux session(Normal mode1)
 
1619     #     (Normal mode1)$ echo {a..g} | ./xpanes -n 2
 
1620     # => This will once create the new window like this.
 
1621     #     (inside of tmux session)$ ./xpanes '-n' '2' 'a' 'b' 'c' 'd' 'e' 'f' 'g'
 
1622     #     => After the window is closed, following panes would be left.
 
1623     #     (pane 1)$ echo a b
 
1624     #     (pane 2)$ echo c d
 
1625     #     (pane 3)$ echo e f
 
1627     # In order to create such the query,
 
1628     # separate all the argument into minimum tokens
 
1630     if [[ -n "${XP_MAX_PANE_ARGS-}" ]]; then
 
1631       _pane_num4new_term=1
 
1637     XP_STDIN+=("${line}")
 
1638   done < <(cat | xpns_rm_empty_line | \
 
1639     xpns_pipe_filter "${_pane_num4new_term:-${XP_MAX_PANE_ARGS}}")
 
1642   # Merge them into command.
 
1643   if [[ -n "${XP_ARGS[*]-}" ]]; then
 
1644     # Attention: It might be wrong result if IFS is changed.
 
1645     XP_CMD_UTILITY="${XP_ARGS[*]}"
 
1648   # If there is empty -I option or user does not assign the <repstr>,
 
1649   # Append the space and <repstr> at the end of the <command>
 
1650   # This is same as the xargs command of GNU.
 
1652   #   $ echo 10 | xargs seq
 
1655   #   $ echo 10 | xargs -I@ seq @
 
1657   if [[ -z "${XP_REPSTR}" ]]; then
 
1658     XP_REPSTR="${XP_DEFAULT_REPSTR}"
 
1659     if [[ -n "${XP_ARGS[*]-}" ]]; then
 
1660       XP_CMD_UTILITY="${XP_ARGS[*]-} ${XP_REPSTR}"
 
1664   # Deal with stdin as arguments.
 
1665   XP_ARGS=("${XP_STDIN[@]-}")
 
1668 xpns_layout_short2long() {
 
1671     -e 's/^eh$/even-horizontal/' \
 
1672     -e 's/^ev$/even-vertical/' \
 
1673     -e 's/^mh$/main-horizontal/' \
 
1674     -e 's/^mv$/main-vertical/' \
 
1678 xpns_is_valid_layout() {
 
1679   local _layout="${1-}"
 
1680   local _pat='^(tiled|even-horizontal|even-vertical|main-horizontal|main-vertical)$'
 
1681   if ! [[ $_layout =~ $_pat ]]  ; then
 
1682     xpns_msg_error "Invalid layout '${_layout}'."
 
1687 xpns_warning_before_extra() {
 
1689   local _synchronized=
 
1690   _synchronized="$(xpns_get_local_tmux_conf "synchronize-panes")"
 
1691   if [[ "on" == "${_synchronized}" ]];then
 
1692     xpns_msg_warning "Panes are now synchronized.
 
1693 '-x' option may cause unexpected behavior on the synchronized panes."
 
1694     printf "Are you really sure? [y/n]: "
 
1696     if ! [[ "${_ans-}" =~ ^[yY]  ]]; then
 
1702 xpns_load_flag_options() {
 
1703   if [[ "$1" =~ h ]]; then
 
1707   if [[ "$1" =~ V ]]; then
 
1711   if [[ "$1" =~ x ]]; then
 
1713     XP_OPT_USE_PRESET_LAYOUT=1 ## Layout presets must be used with -x
 
1714     if ! xpns_warning_before_extra; then
 
1718   if [[ "$1" =~ d ]]; then
 
1721   if [[ "$1" =~ e ]]; then
 
1725   if [[ "$1" =~ t ]]; then
 
1726     if ( xpns_tmux_is_greater_equals 2.3 ) ; then
 
1728       XP_OPT_CHANGE_BORDER=1
 
1730       xpns_msg_warning "-t option cannot be used by tmux version less than 2.3. Disabled."
 
1734   if [[ "$1" =~ s ]]; then
 
1736     XP_OPT_SPEEDY_AWAIT=1
 
1738   if [[ "$1" =~ ss ]]; then
 
1739     XP_OPT_SPEEDY_AWAIT=0
 
1744 xpns_load_arg_options() {
 
1745   # Extract flag options only.
 
1747   xpns_load_flag_options "$(xpns_extract_matched "$1" "^-${XP_FLAG_OPTIONS}+")" > /dev/null
 
1748   if [[ "$1" =~ ^-${XP_FLAG_OPTIONS}*I ]]; then
 
1749     # Behavior like this.
 
1750     # -IAAA       -- XP_REPSTR="AAA"
 
1751     # -I AAA BBB  -- XP_REPSTR="AAA", XP_ARGS=("BBB")
 
1752     # -I"AAA BBB" -- XP_REPSTR="AAA BBB"
 
1753     # -IAAA BBB   -- XP_REPSTR="AAA", XP_ARGS=("BBB")
 
1754     # -I -d ...   -- XP_REPSTR=""
 
1755     _pattern="^-${XP_FLAG_OPTIONS}*I(.+)"
 
1756     if [[ "$1" =~ $_pattern ]]; then
 
1757       XP_REPSTR="${BASH_REMATCH[1]}"
 
1759     elif ! [[ "$2" =~ ^-.* ]]; then
 
1763       xpns_msg_error "invalid argument '$2' for -I option"
 
1766   elif [[ "$1" =~ ^-${XP_FLAG_OPTIONS}*l ]]; then
 
1767     _pattern="^-${XP_FLAG_OPTIONS}*l(.+)"
 
1768     if [[ "$1" =~ $_pattern ]]; then
 
1769       XP_OPT_USE_PRESET_LAYOUT=1
 
1770       XP_LAYOUT="$(cat <<<"${BASH_REMATCH[1]}" | xpns_layout_short2long)"
 
1771       xpns_is_valid_layout "${XP_LAYOUT}"
 
1773     elif ! [[ "$2" =~ ^-.* ]]; then
 
1774       XP_OPT_USE_PRESET_LAYOUT=1
 
1775       XP_LAYOUT="$(cat <<<"$2" |  xpns_layout_short2long )"
 
1776       xpns_is_valid_layout "${XP_LAYOUT}"
 
1779       xpns_msg_error "invalid argument '$2' for -l option"
 
1782   elif [[ "$1" =~ ^-${XP_FLAG_OPTIONS}*c ]]; then
 
1783     _pattern="^-${XP_FLAG_OPTIONS}*c(.+)"
 
1784     if [[ "$1" =~ $_pattern ]]; then
 
1785       XP_CMD_UTILITY="${BASH_REMATCH[1]}"
 
1786       XP_OPT_CMD_UTILITY=1
 
1788     elif ! [[ "$2" =~ ^-.* ]]; then
 
1790       XP_OPT_CMD_UTILITY=1
 
1793       xpns_msg_error "invalid argument '$2' for -c option"
 
1796   elif [[ "$1" =~ ^-${XP_FLAG_OPTIONS}*n ]]; then
 
1797     _pattern="^-${XP_FLAG_OPTIONS}*n([0-9]+)"
 
1798     if [[ "$1" =~ $_pattern ]]; then
 
1799       XP_MAX_PANE_ARGS="${BASH_REMATCH[1]}"
 
1801     elif [[ "$2" =~ ^[0-9]+$ ]]; then
 
1802       XP_MAX_PANE_ARGS="$2"
 
1805       xpns_msg_error "invalid argument '$2' for -n option"
 
1808   elif [[ "$1" =~ ^-${XP_FLAG_OPTIONS}*S ]]; then
 
1809     _pattern="^-${XP_FLAG_OPTIONS}*S(.+)"
 
1810     if [[ "$1" =~ $_pattern ]]; then
 
1811       XP_SOCKET_PATH="${BASH_REMATCH[1]}"
 
1813     elif ! [[ "$2" =~ ^-.* ]]; then
 
1817       xpns_msg_error "invalid argument '$2' for -S option"
 
1820   elif [[ "$1" =~ ^-${XP_FLAG_OPTIONS}*C ]]; then
 
1821     _pattern="^-${XP_FLAG_OPTIONS}*C([0-9]+)"
 
1822     if [[ "$1" =~ $_pattern ]]; then
 
1823       XP_OPT_CUSTOM_SIZE_COLS="${BASH_REMATCH[1]}"
 
1825     elif [[ "$2" =~ ^[0-9]+$ ]];then
 
1826       XP_OPT_CUSTOM_SIZE_COLS="$2"
 
1829       xpns_msg_error "invalid argument '$2' for -C option"
 
1832   elif [[ "$1" =~ ^-${XP_FLAG_OPTIONS}*R ]]; then
 
1833     _pattern="^-${XP_FLAG_OPTIONS}*R([0-9]+)"
 
1834     if [[ "$1" =~ $_pattern ]]; then
 
1835       XP_OPT_CUSTOM_SIZE_ROWS="${BASH_REMATCH[1]}"
 
1837     elif [[ "$2" =~ ^[0-9]+$ ]];then
 
1838       XP_OPT_CUSTOM_SIZE_ROWS="$2"
 
1841       xpns_msg_error "invalid argument '$2' for -R option"
 
1844   elif [[ "$1" =~ ^-${XP_FLAG_OPTIONS}*B ]]; then
 
1845     _pattern="^-${XP_FLAG_OPTIONS}*B(.+)"
 
1846     if [[ "$1" =~ $_pattern ]]; then
 
1847       XP_BEGIN_ARGS+=("${BASH_REMATCH[1]}")
 
1850       XP_BEGIN_ARGS+=("$2")
 
1857 xpns_load_long_options() {
 
1858   if [[ "$1" =~ ^--help$ ]]; then
 
1861   elif [[ "$1" =~ ^--version$ ]]; then
 
1864   elif [[ "$1" =~ ^--desync$ ]]; then
 
1867   elif [[ "$1" =~ ^--log-format=.*$ ]]; then
 
1869     TMUX_XPANES_LOG_FORMAT="${1#--log-format=}"
 
1871   elif [[ "$1" =~ ^--log ]]; then
 
1873     if [[ "$1" =~ ^--log=.*$  ]]; then
 
1874       TMUX_XPANES_LOG_DIRECTORY="${1#--log=}"
 
1877   elif [[ "$1" =~ ^--ssh$ ]]; then
 
1878     XP_CMD_UTILITY="${XP_SSH_CMD_UTILITY}"
 
1879     # Enable -t option as well
 
1881     XP_OPT_CHANGE_BORDER=1
 
1884     XP_OPT_SPEEDY_AWAIT=1
 
1886   elif [[ "$1" =~ ^--stay$ ]]; then
 
1889   elif [[ "$1" =~ ^--cols=[0-9]+$ ]]; then
 
1890     XP_OPT_CUSTOM_SIZE_COLS="${1#--cols=}"
 
1892   elif [[ "$1" =~ ^--rows=[0-9]+$ ]]; then
 
1893     XP_OPT_CUSTOM_SIZE_ROWS="${1#--rows=}"
 
1895   elif [[ "$1" =~ ^--bulk-cols=[0-9,]*[0-9]+$ ]]; then
 
1896     XP_OPT_BULK_COLS="${1#--bulk-cols=}"
 
1898   elif [[ "$1" =~ ^--debug$ ]]; then
 
1901   elif [[ "$1" =~ ^--dry-run$ ]]; then # For unit testing
 
1904   elif [[ "$1" =~ ^--ignore-size-limit$ ]]; then
 
1905     XP_OPT_IGNORE_SIZE_LIMIT=1
 
1912     xpns_msg_error "invalid option -- '${1#--}'"
 
1918 xpns_parse_options() {
 
1919   while (( $# > 0 )); do
 
1922       if [[ ${XP_NO_OPT} -eq 1 ]]; then
 
1926         # Disable any more options
 
1935       if [[ ${XP_NO_OPT} -eq 1 ]]; then
 
1939         local _shift_count="0"
 
1940         xpns_load_long_options "$@"
 
1942         [[ "${_shift_count}" = "1" ]] && XP_OPTIONS+=("$1") && shift
 
1949       if [[ ${XP_NO_OPT} -eq 1 ]]; then
 
1953         local _shift_count="0"
 
1954         if [[ "$1" =~ ^-${XP_FLAG_OPTIONS}*${XP_ARG_OPTIONS}. ]];then
 
1955           xpns_load_arg_options "$@"
 
1956           XP_OPTIONS+=("$1") && shift
 
1957         elif [[ "$1" =~ ^-${XP_FLAG_OPTIONS}*${XP_ARG_OPTIONS}$ ]] && [[ -n "${2-}" ]];then
 
1958           xpns_load_arg_options "$@"
 
1960           XP_OPTIONS+=("$1" "$2") && shift && shift
 
1961         elif [[ "$1" =~ ^-${XP_FLAG_OPTIONS}+$  ]];then
 
1962           xpns_load_flag_options "$1"
 
1963           XP_OPTIONS+=("$1") && shift
 
1968           xpns_msg_error "Invalid option -- '${1#-}'"
 
1985   # If there is any standard input from pipe,
 
1986   # 1 line handled as 1 argument.
 
1987   if [[ ! -t 0 ]]; then
 
1989     xpns_switch_pipe_mode
 
1992   # When no argument arr given, exit.
 
1993   if [[ -z "${XP_ARGS[*]-}" ]]; then
 
1994     xpns_msg_error "No arguments."
 
1999   if [[ -n "${XP_OPT_CUSTOM_SIZE_COLS-}" ]] || [[ -n "${XP_OPT_CUSTOM_SIZE_ROWS-}" ]]; then
 
2000     if [[ "$XP_OPT_EXTRA" -eq 1 ]]; then
 
2001       xpns_msg_warning "The columns/rows options (-C, --cols, -R, --rows) cannot be used with -x option. Ignored."
 
2002     elif [[ "$XP_OPT_EXTRA" -eq 0 ]] && [[ "${XP_OPT_USE_PRESET_LAYOUT}" -eq 1 ]]; then
 
2003       # This part is required to keep backward compatibility.
 
2004       ## Users can simulate xpanes v3.x to set : alias xpanes="xpanes -lt"
 
2005       xpns_msg_info "Columns/rows option (-C, --cols, -R, --rows) and -l option are provided. Disable -l. "
 
2006       XP_OPT_USE_PRESET_LAYOUT=0
 
2010   # Set default value in case of empty.
 
2011   XP_CMD_UTILITY="${XP_CMD_UTILITY:-${XP_DEFAULT_CMD_UTILITY}}"
 
2012   XP_REPSTR="${XP_REPSTR:-${XP_DEFAULT_REPSTR}}"
 
2014   # To set command on pre_execution, set -c option manually.
 
2015   if [[ ${XP_OPT_CMD_UTILITY} -eq 0 ]];then
 
2016     XP_OPTIONS+=("-c" "${XP_CMD_UTILITY}")
 
2021 ## --------------------------------
 
2023 ## --------------------------------
 
2025   xpns_parse_options ${1+"$@"}
 
2026   xpns_check_env "${XP_DEPENDENCIES}"
 
2027   ## --------------------------------
 
2028   # Parameter validation
 
2029   ## --------------------------------
 
2030   # When do dry-run flag is enabled, skip running (this is used to execute unit test of itself).
 
2031   if [[ ${XP_OPT_DRY_RUN} -eq 1 ]]; then
 
2034   # Validate log directory.
 
2035   if [[ ${XP_OPT_LOG_STORE} -eq 1 ]]; then
 
2036     TMUX_XPANES_LOG_DIRECTORY=$(xpns_normalize_directory "${TMUX_XPANES_LOG_DIRECTORY}")
 
2037     xpns_is_valid_directory "${TMUX_XPANES_LOG_DIRECTORY}" && \
 
2038     TMUX_XPANES_LOG_DIRECTORY=$(cd "${TMUX_XPANES_LOG_DIRECTORY}" && pwd)
 
2040   ## --------------------------------
 
2041   # If current shell is outside of tmux session.
 
2042   ## --------------------------------
 
2043   if [[ -z "${TMUX-}" ]]; then
 
2045   ## --------------------------------
 
2046   # If current shell is already inside of tmux session.
 
2047   ## --------------------------------
 
2054 ## --------------------------------
 
2056 ## --------------------------------