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.3"
 
  10 ## trap might be updated in 'xpns_pre_execution' function
 
  11 trap 'rm -f "${XP_CACHE_HOME}"/__xpns_*$$; xpns_clean_session' EXIT
 
  13 ## --------------------------------
 
  15 ## --------------------------------
 
  16 # Invalid option/argument
 
  25 # Impossible layout: Small pane
 
  26 readonly XP_ESMLPANE=7
 
  28 # Log related exit status is 2x.
 
  29 ## Could not create a directory.
 
  30 readonly XP_ELOGDIR=20
 
  32 ## Could not directory to store logs is not writable.
 
  33 readonly XP_ELOGWRITE=21
 
  35 # User's intentional exit is 3x
 
  36 ## User exit the process intentionally by following warning message.
 
  37 readonly XP_EINTENT=30
 
  39 ## All the panes are closed before processing due to user's options/command.
 
  40 readonly XP_ENOPANE=31
 
  42 # Necessary commands are not found
 
  43 readonly XP_ENOCMD=127
 
  47 # XP_THIS_FILE_NAME is supposed to be "xpanes".
 
  48 readonly XP_THIS_FILE_NAME="${0##*/}"
 
  49 readonly XP_THIS_DIR="$(cd "$(dirname "${BASH_SOURCE[0]:-$0}")" && pwd)"
 
  50 readonly XP_ABS_THIS_FILE_NAME="${XP_THIS_DIR}/${XP_THIS_FILE_NAME}"
 
  52 # Prevent cache directory being created under root / directory in any case.
 
  53 # This is quite rare case (but it can be happened).
 
  54 readonly XP_USER_HOME="${HOME:-/tmp}"
 
  56 # Basically xpanes follows XDG Base Direcotry Specification.
 
  57 # https://specifications.freedesktop.org/basedir-spec/basedir-spec-0.6.html
 
  58 XDG_CACHE_HOME="${XDG_CACHE_HOME:-${XP_USER_HOME}/.cache}"
 
  59 readonly XP_CACHE_HOME="${XDG_CACHE_HOME}/xpanes"
 
  61 # This is supposed to be xpanes-12345(PID)
 
  62 readonly XP_SESSION_NAME="${XP_THIS_FILE_NAME}-$$"
 
  63 # Temporary window name is tmp-12345(PID)
 
  64 readonly XP_TMP_WIN_NAME="tmp-$$"
 
  65 readonly XP_EMPTY_STR="EMPTY"
 
  67 readonly XP_SUPPORT_TMUX_VERSION_LOWER="1.8"
 
  69 # Check dependencies just in case.
 
  70 # Even POSIX compliant commands are only used in this program.
 
  71 # `xargs`, `sleep`, `mkfifo` are omitted because minimum functions can work without them.
 
  72 readonly XP_DEPENDENCIES="${XP_DEPENDENCIES:-tmux grep sed tr od echo touch printf cat sort pwd cd mkfifo}"
 
  74 ## --------------------------------
 
  75 # User customizable shell variables
 
  76 ## --------------------------------
 
  77 TMUX_XPANES_EXEC=${TMUX_XPANES_EXEC:-tmux}
 
  78 TMUX_XPANES_PANE_BORDER_FORMAT="${TMUX_XPANES_PANE_BORDER_FORMAT:-#[bg=green,fg=black] #T #[default]}"
 
  79 TMUX_XPANES_PANE_BORDER_STATUS="${TMUX_XPANES_PANE_BORDER_STATUS:-bottom}"
 
  80 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'}
 
  81 XP_DEFAULT_TMUX_XPANES_LOG_FORMAT="[:ARG:].log.%Y-%m-%d_%H-%M-%S"
 
  82 TMUX_XPANES_LOG_FORMAT="${TMUX_XPANES_LOG_FORMAT:-${XP_DEFAULT_TMUX_XPANES_LOG_FORMAT}}"
 
  83 XP_DEFAULT_TMUX_XPANES_LOG_DIRECTORY="${XP_CACHE_HOME}/logs"
 
  84 TMUX_XPANES_LOG_DIRECTORY="${TMUX_XPANES_LOG_DIRECTORY:-${XP_DEFAULT_TMUX_XPANES_LOG_DIRECTORY}}"
 
  86 ## --------------------------------
 
  88 ## --------------------------------
 
  89 # options which work individually.
 
  90 # readonly XP_FLAG_OPTIONS="[hVdetxs]"
 
  91 # options which need arguments.
 
  92 readonly XP_ARG_OPTIONS="[ISclnCRB]"
 
  93 readonly XP_DEFAULT_LAYOUT="tiled"
 
  94 readonly XP_DEFAULT_REPSTR="{}"
 
  95 readonly XP_DEFAULT_CMD_UTILITY="echo {} "
 
  96 readonly XP_SSH_CMD_UTILITY="ssh -o StrictHostKeyChecking=no {} "
 
  97 readonly XP_OFS="${XP_OFS:- }"
 
 108 XP_DEFAULT_SOCKET_PATH_BASE="${XP_CACHE_HOME}/socket"
 
 109 XP_DEFAULT_SOCKET_PATH="${XP_DEFAULT_SOCKET_PATH_BASE}.$$"
 
 110 XP_SOCKET_PATH="${XP_SOCKET_PATH:-${XP_DEFAULT_SOCKET_PATH}}"
 
 114 XP_LAYOUT="${XP_DEFAULT_LAYOUT}"
 
 117 XP_OPT_CHANGE_BORDER=0
 
 120 XP_OPT_SPEEDY_AWAIT=0
 
 121 XP_OPT_USE_PRESET_LAYOUT=0
 
 122 XP_OPT_CUSTOM_SIZE_COLS=
 
 123 XP_OPT_CUSTOM_SIZE_ROWS=
 
 130 XP_OPT_IGNORE_SIZE_LIMIT=0
 
 132 ## --------------------------------
 
 134 #   $1 -- Log level (i.e Warning, Error)
 
 137 #      xpanes:Error: invalid option.
 
 139 # This log format is created with reference to openssl's one.
 
 140 #   $ echo | openssl -a
 
 141 #   openssl:Error: '-a' is an invalid command.
 
 142 ## --------------------------------
 
 146   local _msg="${XP_THIS_FILE_NAME}:${_loglevel}: ${_msgbody}"
 
 147   printf "%s\\n" "${_msg}" >&2
 
 155   xpns_msg "Warning" "$1"
 
 159   if [[ $XP_OPT_DEBUG -eq 1 ]]; then
 
 160     xpns_msg "Debug" "$(date "+[%F_%T]"):${FUNCNAME[1]}:$1"
 
 165   xpns_msg "Error" "$1"
 
 170   echo "Try '${XP_THIS_FILE_NAME} --help' for more information." >&2
 
 175 Usage: ${XP_THIS_FILE_NAME} [OPTIONS] [argument ...]
 
 176 Usage(Pipe mode): command ... | ${XP_THIS_FILE_NAME} [OPTIONS] [<command> ...]
 
 183   ${XP_THIS_FILE_NAME} [OPTIONS] [argument ...]
 
 186   command ... | ${XP_THIS_FILE_NAME} [OPTIONS] [<command> ...]
 
 189   -h,--help                    Display this help and exit.
 
 190   -V,--version                 Output version information and exit.
 
 191   -B <begin-command>           Run <begin-command> before processing <command> in each pane. Multiple options are allowed.
 
 192   -c <command>                 Set <command> to be executed in each pane. Default is \`echo {}\`.
 
 193   -d,--desync                  Make synchronize-panes option off in new window.
 
 194   -e                           Execute given arguments as is. Same as \`-c '{}'\`
 
 195   -I <repstr>                  Replacing one or more occurrences of <repstr> in command provided by -c or -B. Default is \`{}\`.
 
 196   -C NUM,--cols=NUM            Number of columns of window layout.
 
 197   -R NUM,--rows=NUM            Number of rows of window layout.
 
 198   -l <layout>                  Set the preset of window layout. Recognized layout arguments are:
 
 204   -n <number>                  Set the maximum number of <argument> taken for each pane.
 
 205   -s                           Speedy mode: Run command without opening an interactive shell.
 
 206   -ss                          Speedy mode AND close a pane automatically at the same time as process exiting.
 
 207   -S <socket-path>             Set a full alternative path to the server socket.
 
 208   -t                           Display each argument on the each pane's border as their title.
 
 209   -x                           Create extra panes in the current active window.
 
 210   --log[=<directory>]          Enable logging and store log files to ~/.cache/xpanes/logs or <directory>.
 
 211   --log-format=<FORMAT>        Make name of log files follow <FORMAT>. Default is \`${XP_DEFAULT_TMUX_XPANES_LOG_FORMAT}\`.
 
 212   --ssh                        Same as \`-t -s -c 'ssh -o StrictHostKeyChecking=no {}'\`.
 
 213   --stay                       Do not switch to new window.
 
 214   --bulk-cols=NUM1[,NUM2 ...]  Set number of columns on multiple rows (i.e, "2,2,2" represents 2 cols x 3 rows).
 
 215   --debug                      Print debug message.
 
 217 Copyright (c) 2021 Yamada, Yasuhiro
 
 218 Released under the MIT License.
 
 219 https://github.com/greymd/tmux-xpanes
 
 223 # Show version number
 
 225   echo "${XP_THIS_FILE_NAME} ${XP_VERSION}"
 
 228 # Get version number for tmux
 
 229 xpns_get_tmux_version() {
 
 230   local _tmux_version=""
 
 231   if ! ${TMUX_XPANES_EXEC} -V &> /dev/null; then
 
 232     # From tmux 0.9 to 1.3, there is no -V option.
 
 233     _tmux_version="tmux 0.9-1.3"
 
 235     _tmux_version="$( ${TMUX_XPANES_EXEC} -V)"
 
 237   read -r _ _ver <<< "${_tmux_version}"
 
 238   # Strip the leading "next-" part that is present in tmux versions that are
 
 239   # in development. Eg: next-3.3
 
 240   echo "${_ver//next-/}"
 
 243 # Check whether the prefered tmux version is greater than host's tmux version.
 
 244 # $1 ... Prefered version.
 
 245 # $2 ... Host tmux version(optional).
 
 246 # In case of tmux version is 1.7, the result will be like this.
 
 247 # 0 is true, 1 is false.
 
 256 xpns_tmux_is_greater_equals() {
 
 257   local _check_version="$1"
 
 258   local _tmux_version="${2:-$(xpns_get_tmux_version)}"
 
 259   # Simple numerical comparison does not work because there is the version like "1.9a".
 
 260   if [[ "$( printf "%s\\n%s" "${_tmux_version}" "${_check_version}" | sort -n | head -n 1)" != "${_check_version}" ]]; then
 
 267 xpns_get_local_tmux_conf() {
 
 268   local _conf_name="$1"
 
 269   local _session="${2-}"
 
 271     if [[ -z "${_session-}" ]]; then
 
 272       ${TMUX_XPANES_EXEC} show-window-options
 
 274       ${TMUX_XPANES_EXEC} -S "${_session}" show-window-options
 
 276   } | grep "^${_conf_name}" |
 
 279       printf "%s\\n" "${_v}"
 
 283 xpns_get_global_tmux_conf() {
 
 284   local _conf_name="$1"
 
 285   local _session="${2-}"
 
 287     if [[ -z "${_session-}" ]]; then
 
 288       ${TMUX_XPANES_EXEC} show-window-options -g
 
 290       ${TMUX_XPANES_EXEC} -S "${_session}" show-window-options -g
 
 292   } | grep "^${_conf_name}" |
 
 295       printf "%s\\n" "${_v}"
 
 299 # Disable allow-rename because
 
 300 # window separation does not work correctly
 
 301 # if "allow-rename" option is on
 
 302 xpns_suppress_allow_rename()  {
 
 303   local _default_allow_rename="$1"
 
 304   local _session="${2-}"
 
 305   if [[ "${_default_allow_rename-}" == "on"  ]]; then
 
 306     ## Temporary, disable "allow-rename"
 
 307     xpns_msg_debug "'allow-rename' option is 'off' temporarily."
 
 308     if [[ -z "${_session-}" ]]; then
 
 309       ${TMUX_XPANES_EXEC} set-window-option -g allow-rename off
 
 311       ${TMUX_XPANES_EXEC} -S "${_session}" set-window-option -g allow-rename off
 
 316 # Restore default "allow-rename"
 
 317 # Do not write like 'xpns_restore_allow_rename "some value" "some value" > /dev/null'
 
 318 # In tmux 1.6, 'tmux set-window-option' might be stopped in case of redirection.
 
 319 xpns_restore_allow_rename()  {
 
 320   local _default_allow_rename="$1"
 
 321   local _session="${2-}"
 
 322   if [[ "${_default_allow_rename-}" == "on"  ]]; then
 
 323     xpns_msg_debug "Restore original value of 'allow-rename' option."
 
 324     if [[ -z "${_session-}" ]]; then
 
 325       ${TMUX_XPANES_EXEC} set-window-option -g allow-rename on
 
 327       ${TMUX_XPANES_EXEC} -S "${_session}" set-window-option -g allow-rename on
 
 334 # 11 / 2 = 5.5 => ceiling => 6
 
 339   printf "%s\\n" $(((_divide + _by - 1) / _by))
 
 344 # Divide 10 into 3 parts as equally as possible.
 
 345 xpns_divide_equally()  {
 
 349   local _upper _lower _upper_count _lower_count
 
 350   _upper="$(xpns_ceiling "$_number" "$_count")"
 
 351   _lower=$((_upper - 1))
 
 352   _lower_count=$((_upper * _count - _number))
 
 353   _upper_count=$((_count - _lower_count))
 
 354   eval "printf '${_upper} %.0s' {1..$_upper_count}"
 
 355   ((_lower_count > 0))   && eval "printf '${_lower} %.0s' {1..$_lower_count}"
 
 358 # echo 3 3 3 3 | func
 
 360 xpns_nums_accumulate_sum()  {
 
 365   done < <( cat | tr ' ' '\n')
 
 371 # For example, "3 2 2 2" represents following cell positions
 
 373 # 1 [] [] [] => 3 rows
 
 378 # After the transposition, it must be "4 4 1" which represents below
 
 380 # 1 [] [] [] [] => 4 rows
 
 381 # 2 [] [] [] [] => 4 rows
 
 383 xpns_nums_transpose()  {
 
 387   xpns_msg_debug "column num = $_colnum, input = $*"
 
 388   _spaces="$(for i in "$@"; do
 
 392   # 'for' statement does not work somehow
 
 393   _result="$(while read -r i; do
 
 394     ## This part is depending on the following 'cut' behavior
 
 395     ## $ echo 1234 | cut -c 5
 
 396     ## => result is supposed to be empty
 
 397     printf "%s\\n" "$_spaces" | cut -c "$i" | grep -c ' '
 
 398   done < <(xpns_seq 1 "${_colnum}") | xpns_newline2space)"
 
 399   xpns_msg_debug "result = $_result"
 
 400   printf "%s\\n" "$_result"
 
 403 # Adjust size of columns and rows in accordance with given N
 
 404 # func <col> <row> <N>
 
 410 xpns_adjust_col_row() {
 
 419   ((col != 0))   && fix_col_flg=1 || fix_col_flg=0
 
 420   ((row != 0))   && fix_row_flg=1 || fix_row_flg=0
 
 422   # This is just a author (@greymd)'s preference.
 
 423   if ((fix_col_flg == 0))   && ((fix_row_flg == 0))   && ((N == 2)); then
 
 426     printf "%d %d\\n" "${col}" "${row}"
 
 430   # If both values are provided, col is used.
 
 431   if ((fix_col_flg == 1))   && ((fix_row_flg == 1)); then
 
 435   # This algorhythm is almost same as tmux default
 
 436   #   https://github.com/tmux/tmux/blob/2.8/layout-set.c#L436
 
 437   while ((col * row < N)); do
 
 438     ((fix_row_flg != 1))   && ((row = row + 1))
 
 439     if ((col * row < N)); then
 
 440       ((fix_col_flg != 1))   &&  ((col = col + 1))
 
 443   printf "%d %d\\n" "${col}" "${row}"
 
 446 # Make each line unique by adding index number
 
 447 # echo aaa bbb ccc aaa ccc ccc | xargs -n 1 | xpns_unique_line
 
 455 # Eval is used because associative array is not supported before bash 4.2
 
 458   while read -r line; do
 
 459     _val_name="__xpns_hash_$(printf "%s" "${line}" | xpns_value2key)"
 
 460     # initialize variable
 
 461     eval "${_val_name}=\${${_val_name}:-0}"
 
 463     eval "${_val_name}=\$(( ++${_val_name} ))"
 
 464     printf "%s\\n" "${line}-$(eval printf "%s" "\$${_val_name}")"
 
 469 # Generate log file names from given arguments.
 
 471 #        echo <arg1> <arg2> ... | xpns_log_filenames <FORMAT>
 
 475 #        $ echo aaa bbb ccc aaa ccc ccc | xargs -n 1 | xpns_log_filenames '[:ARG:]_[:PID:]_%Y%m%d.log'
 
 476 #        aaa-1_1234_20160101.log
 
 477 #        bbb-1_1234_20160101.log
 
 478 #        ccc-1_1234_20160101.log
 
 479 #        aaa-2_1234_20160101.log
 
 480 #        ccc-2_1234_20160101.log
 
 481 #        ccc-3_1234_20160101.log
 
 483 xpns_log_filenames()  {
 
 486   _full_fmt="$(date "+${_arg_fmt}")"
 
 488     # 1st argument + '-' + unique number (avoid same argument has same name)
 
 490     while   read -r _arg; do
 
 491       cat <<< "${_full_fmt}" |
 
 492         sed   "s/\\[:ARG:\\]/${_arg}/g" |
 
 493         sed   "s/\\[:PID:\\]/$$/g"
 
 497 ## --------------------------------
 
 498 # Normalize directory by making following conversion.
 
 500 #  * Remove the slash '/' at the end of the dirname.
 
 502 #        xpns_normalize_directory <direname>
 
 504 #        Normalized <dirname>
 
 505 ## --------------------------------
 
 506 xpns_normalize_directory() {
 
 508   # Remove end of slash '/'
 
 511   _dir="${_dir/#~/${HOME}}"
 
 512   printf "%s\\n" "${_dir}"
 
 515 ## --------------------------------
 
 516 # Ensure existence of given directory
 
 518 #        xpns_is_valid_directory <direname>
 
 520 #        Absolute path of the <dirname>
 
 521 ## --------------------------------
 
 522 xpns_is_valid_directory() {
 
 524   local _checkfile="${XP_THIS_FILE_NAME}.$$"
 
 526   if [[ ! -d "${_dir}" ]]; then
 
 528     if mkdir "${_dir}"; then
 
 529       xpns_msg_info "${_dir} is created."
 
 531       xpns_msg_error "Failed to create ${_dir}"
 
 535   # Try to create file.
 
 536   #   Not only checking directory permission,
 
 537   #   but also i-node and other misc situations.
 
 538   if ! touch "${_dir}/${_checkfile}"; then
 
 539     xpns_msg_error "${_dir} is not writable."
 
 540     rm -f "${_dir}/${_checkfile}"
 
 543   rm -f "${_dir}/${_checkfile}"
 
 546 # Convert array to string which is can be used as command line argument.
 
 548 #       xpns_arr2args <array object>
 
 550 #       array=(aaa bbb "ccc ddd" eee "f'f")
 
 551 #       xpns_arr2args "${array[@]}"
 
 552 #       @returns "'aaa' 'bbb' 'ccc ddd' 'eee' 'f\'f'"
 
 556   # If there is no argument, usage will be shown.
 
 557   if [[ $# -lt 1 ]]; then
 
 562     # Use 'cat <<<"input"' command instead of 'echo',
 
 563     # because such the command recognizes option like '-e'.
 
 565       # Escaping single quotations.
 
 566       sed "s/'/'\"'\"'/g" |
 
 567       # Surround argument with single quotations.
 
 568       sed "s/^/'/;s/$/' /" |
 
 574 # Extract first field to generate window name.
 
 575 # ex, $2     =  'aaa bbb ccc'
 
 576 #   return   =  aaa-12345(PID)
 
 577 xpns_generate_window_name() {
 
 578   local _unprintable_str="${1-}"
 
 580   # Leave first 200 characters to prevent
 
 581   # the name exceed the maximum length of tmux window name (2000 byte).
 
 582   printf "%s\\n" "${1:-${_unprintable_str}}" |
 
 583     (   read -r _name _ && printf "%s\\n" "${_name:0:200}-$$" )
 
 586 # Convert any string (including multi-byte chars) to another string
 
 587 # which can be handled as tmux window name.
 
 589   od -v -tx1 -An  | tr -dc 'a-zA-Z0-9' | tr -d '\n'
 
 592 # Restore string encoded by xpns_value2key function.
 
 595   # shellcheck disable=SC2059
 
 596   printf "$(printf "%s" "$_key" | sed 's/../\\x&/g')"
 
 600 # This function behaves like `awk NF`
 
 601 xpns_rm_empty_line() {
 
 605   } | while IFS= read -r line; do
 
 606     # shellcheck disable=SC2086
 
 608     if [[ $# != 0 ]]; then
 
 609       printf "%s\\n" "${line}"
 
 614 # Enable logging feature to the all the panes in the target window.
 
 615 xpns_enable_logging() {
 
 616   local _window_name="$1"
 
 618   local _index_offset="$1"
 
 622   local _log_format="$1"
 
 624   local _unprintable_str="$1"
 
 627   local _args_num=$(($# - 1))
 
 628   # Generate log files from arguments.
 
 630   while read -r _logfile; do
 
 632     xpns_msg_debug "Start logging pipe-pane(cat >> '${_log_dir}/${_logfile}')"
 
 633     ${TMUX_XPANES_EXEC} \
 
 634       pipe-pane -t "${_window_name}.$((_idx + _index_offset))" \
 
 635       "cat >> '${_log_dir}/${_logfile}'" # Tilde expansion does not work here.
 
 638     for i in $(xpns_seq 0 "${_args_num}"); do
 
 639       # Replace empty string.
 
 640       printf "%s\\n" "${_args[i]:-${_unprintable_str}}"
 
 641     done | xpns_log_filenames "${_log_format}"
 
 645 ## Print "1" on the particular named pipe
 
 650   _fifo="${XP_CACHE_HOME}/__xpns_${_wait_id}"
 
 651   xpns_msg_debug "Notify to $_fifo"
 
 652   printf "%s\\n" 1 > "$_fifo" &
 
 655 xpns_notify_logging() {
 
 656   local _window_name="$1"
 
 658   local _args_num=$(($# - 1))
 
 659   for i in $(xpns_seq 0 "${_args_num}"); do
 
 660     xpns_notify "log_${_window_name}-${i}-$$"
 
 665   local _window_name="$1"
 
 667   local _args_num=$(($# - 1))
 
 668   for i in $(xpns_seq 0 "${_args_num}"); do
 
 669     xpns_notify "sync_${_window_name}-${i}-$$" &
 
 673 xpns_is_window_alive() {
 
 674   local _window_name="$1"
 
 676   local _speedy_await_flag="$1"
 
 678   local _def_allow_rename="$1"
 
 680   if ! ${TMUX_XPANES_EXEC} display-message -t "$_window_name" -p > /dev/null 2>&1; then
 
 681     xpns_msg_info "All the panes are closed before displaying the result."
 
 682     if [[ "${_speedy_await_flag}" -eq 0 ]]; then
 
 683       xpns_msg_info "Use '-s' option instead of '-ss' option to avoid this behavior."
 
 685     xpns_restore_allow_rename "${_def_allow_rename-}"
 
 690 xpns_inject_title() {
 
 691   local _target_pane="$1"
 
 696   _pane_tty="$( ${TMUX_XPANES_EXEC} display-message -t "${_target_pane}" -p "#{pane_tty}")"
 
 697   printf "\\033]2;%s\\033\\\\" "${_message}" > "${_pane_tty}"
 
 698   xpns_msg_debug "target_pane=${_target_pane} pane_title=${_message} pane_tty=${_pane_tty}"
 
 701 xpns_is_pane_title_required() {
 
 702   local _title_flag="$1"
 
 704   local _extra_flag="$1"
 
 706   local _pane_border_status=
 
 707   _pane_border_status=$(xpns_get_local_tmux_conf "pane-border-status")
 
 708   if [[ $_title_flag -eq 1 ]]; then
 
 710   elif [[ ${_extra_flag} -eq 1 ]] &&
 
 711     [[ "${_pane_border_status}" != "off"    ]] &&
 
 712     [[ -n "${_pane_border_status}"    ]]; then
 
 714     # Even the -t option is not specified, it is required to inject pane title here.
 
 715     # Because user expects the title is displayed on the pane if the original window is
 
 716     # generated from tmux-xpanes with -t option.
 
 722 # Set pane titles for each pane for -t option
 
 724   local _window_name="$1"
 
 726   local _index_offset="$1"
 
 731     _pane_index=$((_index + _index_offset))
 
 732     xpns_inject_title "${_window_name}.${_pane_index}" "${arg}"
 
 733     _index=$((_index + 1))
 
 737 # Send command to the all the panes in the target window.
 
 738 xpns_send_commands() {
 
 739   local _window_name="$1"
 
 741   local _index_offset="$1"
 
 751     _exec_cmd="${_cmd//${_repstr}/${arg}}"
 
 752     _pane_index=$((_index + _index_offset))
 
 753     ${TMUX_XPANES_EXEC} send-keys -t "${_window_name}.${_pane_index}" "${_exec_cmd}" C-m
 
 754     _index=$((_index + 1))
 
 758 # Separate window vertically, when the number of panes is 1 or 2.
 
 759 xpns_organize_panes() {
 
 760   local _window_name="$1"
 
 766   if [[ "${_args_num}" -eq 1 ]]; then
 
 767     ${TMUX_XPANES_EXEC} select-layout -t "${_window_name}" even-horizontal
 
 768   elif [[ "${_args_num}" -gt 1 ]]; then
 
 769     ${TMUX_XPANES_EXEC} select-layout -t "${_window_name}" tiled
 
 774   if [[ "${XP_LAYOUT}" != "${XP_DEFAULT_LAYOUT}" ]]; then
 
 775     ${TMUX_XPANES_EXEC} select-layout -t "${_window_name}" "${XP_LAYOUT}"
 
 780 # Generate sequential number descending order.
 
 781 # seq is not used because old version of
 
 782 # seq does not generate descending order.
 
 792   eval "printf \"%d\\n\" {$_num1..$_num2}"
 
 797   local _fifo="${XP_CACHE_HOME}/__xpns_${_wait_id}"
 
 798   local _arr=("$_fifo")
 
 800   _fifo_arg=$(xpns_arr2args "${_arr[@]}")
 
 801   xpns_msg_debug "mkfifo $_fifo"
 
 803   xpns_msg_debug "grep -q 1 ${_fifo_arg}"
 
 804   printf "%s\\n" "grep -q 1 ${_fifo_arg}"
 
 807 # Split a new window into multiple panes.
 
 809 xpns_split_window() {
 
 810   local _window_name="$1"
 
 814   local _title_flag="$1"
 
 816   local _speedy_flag="$1"
 
 818   local _await_flag="$1"
 
 820   local _pane_base_index="$1"
 
 824   local _cmd_template="$1"
 
 829   _last_idx=$((${#args[@]} - 1))
 
 831   for i in $(xpns_seq $_last_idx 0); do
 
 832     xpns_msg_debug "Index:${i} Argument:${args[i]}"
 
 833     _sep_count=$((_sep_count + 1))
 
 834     _exec_cmd="${_cmd_template//${_repstr}/${args[i]}}"
 
 837     if [[ $_speedy_flag -eq 1 ]]; then
 
 839       _exec_cmd=$(xpns_inject_wait_command "${_log_flag}" "${_title_flag}" "${_speedy_flag}" "${_await_flag}" "$i" "${_exec_cmd}")
 
 840       # Execute command as a child process of default-shell.
 
 841       ${TMUX_XPANES_EXEC} split-window -t "${_window_name}" -h -d "${_exec_cmd}"
 
 843       # Open login shell and execute command on the interactive screen.
 
 844       ${TMUX_XPANES_EXEC} split-window -t "${_window_name}" -h -d
 
 846     # Restraining that size of pane's width becomes
 
 847     # less than the minimum size which is defined by tmux.
 
 848     if [[ ${_sep_count} -gt 2 ]]; then
 
 849       ${TMUX_XPANES_EXEC} select-layout -t "${_window_name}" tiled
 
 855 # Create new panes on existing window.
 
 857 #    func <window name> <offset of index> <number of pane>
 
 859 xpns_prepare_extra_panes() {
 
 860   local _window_name="$1"
 
 862   local _pane_base_index="$1"
 
 866   local _title_flag="$1"
 
 868   local _speedy_flg="$1"
 
 870   local _await_flg="$1"
 
 872   # specify a pane which has the biggest index number.
 
 873   #   Because pane_id may not be immutable.
 
 874   #   If the small number of index is specified here, correspondance between pane_title and command can be slip off.
 
 875   ${TMUX_XPANES_EXEC} select-pane -t "${_window_name}.${_pane_base_index}"
 
 877   # split window into multiple panes
 
 884     "${_pane_base_index}" \
 
 888 xpns_get_joined_begin_commands()  {
 
 890   if [[ "${#XP_BEGIN_ARGS[*]}" -lt 1 ]]; then
 
 891     printf "%s" "${_commands}"
 
 894   printf "%s\\n" "${XP_BEGIN_ARGS[@]}" "${_commands}"
 
 897 xpns_inject_wait_command()  {
 
 900   local _title_flag="$1"
 
 902   local _speedy_flg="$1"
 
 904   local _await_flg="$1"
 
 911   ## Speedy mode + logging
 
 912   if [[ "${_log_flag}" -eq 1 ]] && [[ "${_speedy_flg}" -eq 1 ]]; then
 
 913     # Wait for start of logging
 
 914     # Without this part, logging thread may start after new process is finished.
 
 915     # Execute function to wait for logging start.
 
 916     _exec_cmd="$(xpns_wait_func "log_${_window_name}-${_idx}-$$")"$'\n'"${_exec_cmd}"
 
 919   ## Speedy mode (Do not allow to close panes before the separation is finished).
 
 920   if [[ "${_speedy_flg}" -eq 1 ]]; then
 
 921     _exec_cmd="$(xpns_wait_func "sync_${_window_name}-${_idx}-$$")"$'\n'${_exec_cmd}
 
 924   ## -s: Speedy mode (Not -ss: Speedy mode + nowait)
 
 925   if [[ "${_await_flg}" -eq 1 ]]; then
 
 927     _msg="$(xpns_arr2args "${TMUX_XPANES_PANE_DEAD_MESSAGE}" | sed 's/"/\\"/g')"
 
 928     _exec_cmd="${_exec_cmd}"$'\n'"${XP_SHELL} -c \"printf -- ${_msg} >&2 && read\""
 
 930   printf "%s" "${_exec_cmd}"
 
 934   local _window_name="$1"
 
 936   local _attach_flg="$1"
 
 938   local _speedy_flg="$1"
 
 945   if [[ "${_attach_flg}" -eq 1 ]]; then
 
 946     if [[ "${_speedy_flg}" -eq 1 ]]; then
 
 947       _window_id=$(${TMUX_XPANES_EXEC} new-window -n "${_window_name}" -F '#{window_id}' -P "${_exec_cmd}")
 
 949       _window_id=$(${TMUX_XPANES_EXEC} new-window -n "${_window_name}" -F '#{window_id}' -P)
 
 953     if [[ "${_speedy_flg}" -eq 1 ]]; then
 
 954       _window_id=$(${TMUX_XPANES_EXEC} new-window -n "${_window_name}" -F '#{window_id}' -P -d "${_exec_cmd}")
 
 956       _window_id=$(${TMUX_XPANES_EXEC} new-window -n "${_window_name}" -F '#{window_id}' -P -d)
 
 959   printf "%s" "${_window_id}"
 
 962 xpns_new_pane_vertical()  {
 
 963   local _window_id="$1"
 
 965   local _cell_height="$1"
 
 967   local _speedy_flg="$1"
 
 972   if [[ "${_speedy_flg}" -eq 1 ]]; then
 
 973     _pane_id="$(${TMUX_XPANES_EXEC} split-window -t "$_window_id" -v -d -l "${_cell_height}" -F '#{pane_id}' -P "${_exec_cmd}")"
 
 975     _pane_id="$(${TMUX_XPANES_EXEC} split-window -t "$_window_id" -v -d -l "${_cell_height}" -F '#{pane_id}' -P)"
 
 977   printf "%s\\n" "${_pane_id}"
 
 980 xpns_split_pane_horizontal()  {
 
 981   local _target_pane_id="$1"
 
 983   local _cell_width="$1"
 
 985   local _speedy_flg="$1"
 
 989   if [[ "${_speedy_flg}" -eq 1 ]]; then
 
 990     ${TMUX_XPANES_EXEC} split-window -t "$_target_pane_id" -h -d -l "$_cell_width" "${_exec_cmd}"
 
 992     ${TMUX_XPANES_EXEC} split-window -t "$_target_pane_id" -h -d -l "$_cell_width"
 
 996 xpns_prepare_window()  {
 
 997   local _window_name="$1"
 
1001   local _title_flag="$1"
 
1003   local _attach_flg="$1"
 
1005   local _speedy_flg="$1"
 
1007   local _await_flg="$1"
 
1011   local _cmd_template="$1"
 
1014   local _window_height="$XP_WINDOW_HEIGHT"
 
1015   local _window_width="$XP_WINDOW_WIDTH"
 
1016   local _col="$XP_OPT_CUSTOM_SIZE_COLS"
 
1017   local _row="$XP_OPT_CUSTOM_SIZE_ROWS"
 
1018   local _cols=("${XP_COLS[@]}")
 
1019   local _cols_offset=("${XP_COLS_OFFSETS[@]}")
 
1022   local _first_pane_id=
 
1026   local _top_pane_height=
 
1027   local _current_pane_width=
 
1034   _cell_height=$(((_window_height - _row + 1) / _row))
 
1035   ## Insert first element
 
1036   _exec_cmd="${_cmd_template//${_repstr}/${_args[0]}}"
 
1037   _exec_cmd="$(xpns_inject_wait_command "${_log_flag}" "${_title_flag}" "${_speedy_flg}" "${_await_flg}" 0 "${_exec_cmd}")"
 
1038   _window_id=$(xpns_new_window "${_window_name}" "${_attach_flg}" "${_speedy_flg}" "${_exec_cmd}")
 
1039   _first_pane_id=$(${TMUX_XPANES_EXEC} display-message -t "$_window_id" -p -F '#{pane_id}' | head -n 1)
 
1041   ## Start from last row
 
1042   for ((i = _row - 1; i > 0; i--)); do
 
1044     _cell_width=$(((_window_width - _col + 1) / _col))
 
1045     xpns_msg_debug "_col=$_col"
 
1046     ((_offset = _cols_offset[i]))
 
1047     for ((j = 0; j < _col; j++)); do
 
1049         ((idx = _offset - _col))
 
1051         # Insert first element of the row first
 
1052         _exec_cmd="${_cmd_template//${_repstr}/${_args[idx]}}"
 
1053         _exec_cmd="$(xpns_inject_wait_command "${_log_flag}" "${_title_flag}" "${_speedy_flg}" "${_await_flg}" "${idx}" "${_exec_cmd}")"
 
1054         _pane_id=$(xpns_new_pane_vertical "${_window_name}" "${_cell_height}" "${_speedy_flg}" "${_exec_cmd}")
 
1056       # Separate row into columns
 
1058         ((idx = _offset - j))
 
1059         _exec_cmd="${_cmd_template//${_repstr}/${_args[idx]}}"
 
1060         _exec_cmd="$(xpns_inject_wait_command "${_log_flag}" "${_title_flag}" "${_speedy_flg}" "${_await_flg}" "${idx}" "${_exec_cmd}")"
 
1061         ## Separate row into columns
 
1062         _current_pane_width=$(${TMUX_XPANES_EXEC} display-message -t "$_pane_id" -p '#{pane_width}' | head -n 1)
 
1063         _rest_col=$((_col - j + 1))
 
1064         _cell_width=$(((_current_pane_width - _rest_col + 1) / _rest_col))
 
1065         xpns_split_pane_horizontal "$_pane_id" "$_cell_width" "${_speedy_flg}" "${_exec_cmd}"
 
1070     _top_pane_height=$(${TMUX_XPANES_EXEC} display-message -t "$_window_id" -p '#{pane_height}' | head -n 1)
 
1072     xpns_msg_debug "_top_pane_height=$_top_pane_height _rest_row=$_rest_row"
 
1073     _cell_height=$(((_top_pane_height - _rest_row + 1) / _rest_row))
 
1076   # Split first row into columns
 
1078   _cell_width=$(((_window_width - _col + 1) / _col))
 
1079   for ((j = 1; j < _col; j++)); do
 
1080     idx=$((_cols_offset[0] - j))
 
1082     _current_pane_width=$(${TMUX_XPANES_EXEC} display-message -t "$_first_pane_id" -p '#{pane_width}' | head -n 1)
 
1083     _rest_col=$((_col - j + 1))
 
1084     _cell_width=$(((_current_pane_width - _rest_col + 1) / _rest_col))
 
1085     ## Split top row into columns
 
1086     _exec_cmd="${_cmd_template//${_repstr}/${_args[idx]}}"
 
1087     _exec_cmd="$(xpns_inject_wait_command "${_log_flag}" "${_title_flag}" "${_speedy_flg}" "${_await_flg}" "${idx}" "${_exec_cmd}")"
 
1088     xpns_split_pane_horizontal "${_first_pane_id}" "${_cell_width}" "${_speedy_flg}" "${_exec_cmd}"
 
1092 xpns_is_session_running() {
 
1094   ${TMUX_XPANES_EXEC} -S "${_socket}" list-session > /dev/null 2>&1
 
1097 # Remove unnecessary session files as much as possible
 
1098 # to make xpanes avoid loading old .tmux.conf.
 
1099 xpns_clean_session() {
 
1100   if [[ "${XP_SOCKET_PATH}" != "${XP_DEFAULT_SOCKET_PATH}" ]]; then
 
1103   # Delete old socket file (xpanes v3.1.0 or before).
 
1104   if [[ -e "${XP_DEFAULT_SOCKET_PATH_BASE}" ]]; then
 
1105     if ! xpns_is_session_running "${XP_DEFAULT_SOCKET_PATH_BASE}"; then
 
1106       xpns_msg_debug "socket(${XP_DEFAULT_SOCKET_PATH_BASE}) is not running. Remove it"
 
1107       rm -f "${XP_DEFAULT_SOCKET_PATH_BASE}"
 
1110   for _socket in "${XP_CACHE_HOME}"/socket.*; do
 
1111     xpns_msg_debug "file = ${_socket}"
 
1112     if ! xpns_is_session_running "${_socket}"; then
 
1113       xpns_msg_debug "socket(${_socket}) is not running. Remove it"
 
1116       xpns_msg_debug "socket(${_socket}) is running. Keep ${_socket}"
 
1122 # Split a new window into multiple panes.
 
1124 #    xpns_prepare_preset_layout_window <window name> <offset of index> <number of pane> <attach or not>
 
1126 xpns_prepare_preset_layout_window() {
 
1127   local _window_name="$1"
 
1129   local _pane_base_index="$1"
 
1131   local _log_flag="$1"
 
1133   local _title_flag="$1"
 
1135   local _attach_flg="$1"
 
1137   local _speedy_flg="$1"
 
1139   local _await_flg="$1"
 
1141   # Create new window.
 
1142   if [[ "${_attach_flg}" -eq 1 ]]; then
 
1143     ${TMUX_XPANES_EXEC} new-window -n "${_window_name}"
 
1146     ${TMUX_XPANES_EXEC} new-window -n "${_window_name}" -d
 
1149   # specify a pane which has the youngest number of index.
 
1150   ${TMUX_XPANES_EXEC} select-pane -t "${_window_name}.${_pane_base_index}"
 
1152   # split window into multiple panes
 
1159     "${_pane_base_index}" \
 
1162   ### If the first pane is still remaining,
 
1163   ### panes cannot be organized well.
 
1164   # Delete the first pane
 
1165   ${TMUX_XPANES_EXEC} kill-pane -t "${_window_name}.${_pane_base_index}"
 
1167   # Select second pane here.
 
1168   #   If the command gets error, it would most likely be caused by user (XP_ENOPANE).
 
1169   #   Suppress error message here and announce it in xpns_execution.
 
1170   ${TMUX_XPANES_EXEC} select-pane -t "${_window_name}.${_pane_base_index}" > /dev/null 2>&1
 
1173 # Check whether given command is in the PATH or not.
 
1176   while read -r cmd; do
 
1177     if ! type "${cmd}" > /dev/null 2>&1; then
 
1178       if [[ "${cmd}" == "tmux" ]] && [[ "${TMUX_XPANES_EXEC}" == "tmux" ]]; then
 
1179         xpns_msg_error "${cmd} is required. Install ${cmd} or set TMUX_XPANES_EXEC variable."
 
1181       elif [[ "${cmd}" != "tmux" ]]; then
 
1182         xpns_msg_error "${cmd} is required."
 
1186   done < <(echo "${_cmds}" | tr ' ' '\n')
 
1188   if ! mkdir -p "${XP_CACHE_HOME}"; then
 
1189     xpns_msg_warning "failed to create cache directory '${XP_CACHE_HOME}'."
 
1192   # Do not omit this part, this is used by testing.
 
1193   TMUX_XPANES_TMUX_VERSION="${TMUX_XPANES_TMUX_VERSION:-$(xpns_get_tmux_version)}"
 
1194   if ( xpns_tmux_is_greater_equals \
 
1195     "${XP_SUPPORT_TMUX_VERSION_LOWER}" \
 
1196     "${TMUX_XPANES_TMUX_VERSION}" ); then
 
1197     : "Supported tmux version"
 
1200       "'${XP_THIS_FILE_NAME}' may not work correctly! Please check followings.
 
1201 * tmux is installed correctly.
 
1202 * Supported tmux version is installed.
 
1203   Version ${XP_SUPPORT_TMUX_VERSION_LOWER} and over is officially supported."
 
1209 xpns_pipe_filter() {
 
1210   local _number="${1-}"
 
1211   if [[ -z "${_number-}" ]]; then
 
1214     xargs -n "${_number}"
 
1218 # Merge array's element by combining with space.
 
1222 #   => array is going to be ("1 2 3" "4 5")
 
1223 xpns_merge_array_elements() {
 
1225   local _pane_num="$1"
 
1227   local _arr_name="$1"
 
1229   local _filtered_args=()
 
1230   eval 'set -- "${'"$_arr_name"'[@]}"'
 
1232   for ((i = 1; i <= _num; i++)); do
 
1233     if [[ -z "$_line" ]]; then
 
1236       _line="${_line}${XP_OFS}$1"
 
1239     if ((i % _pane_num == 0)); then
 
1240       _filtered_args+=("${_line}")
 
1244   if [[ -n "$_line" ]]; then
 
1245     _filtered_args+=("${_line}")
 
1247   eval "$_arr_name"'=("${_filtered_args[@]}")'
 
1250 xpns_newline2space() {
 
1252   while read -r _line; do
 
1253     if [[ -z "$_result" ]]; then
 
1256       _result="${_result}${XP_OFS}${_line}"
 
1259   printf "%s\\n" "${_result}"
 
1262 xpns_get_window_height_width() {
 
1267   local _pattern='^([0-9]+)[ \t]+([0-9]+)$'
 
1269   if ! type stty > /dev/null 2>&1; then
 
1270     xpns_msg_debug "'stty' does not exist: Failed to get window height and size. Skip checking"
 
1274   ## This condition is used for unit testing
 
1275   if [[ -z "${XP_IS_PIPE_MODE-}" ]]; then
 
1276     if [[ ! -t 0 ]]; then
 
1280   if [[ $XP_IS_PIPE_MODE -eq 0 ]]; then
 
1281     if _result=$(stty size 2> /dev/null) && [[ "$_result" =~ $_pattern ]]; then
 
1282       _height="${BASH_REMATCH[1]}"
 
1283       _width="${BASH_REMATCH[2]}"
 
1284       xpns_msg_debug "window height: $_height, width: $_width"
 
1285       printf "%s\\n" "$_height $_width"
 
1289     if ! type ps > /dev/null 2>&1; then
 
1290       xpns_msg_debug "'ps' does not exist: Failed to get window height and size. Skip checking"
 
1294       read -r       # Remove first line
 
1296     } < <(ps -o tty -p $$ 2> /dev/null)
 
1297     ## If it's Linux, -F option is used
 
1298     if _result=$(stty -F "/dev/${_dev}" size 2> /dev/null) && [[ "$_result" =~ $_pattern ]]; then
 
1299       _height="${BASH_REMATCH[1]}"
 
1300       _width="${BASH_REMATCH[2]}"
 
1301       xpns_msg_debug "window height: $_height, width: $_width"
 
1302       printf "%s\\n" "$_height $_width"
 
1305     ## If it's BSD, macOS, -F option is used
 
1306     if _result=$(stty -f "/dev/${_dev}" size 2> /dev/null) && [[ "$_result" =~ $_pattern ]]; then
 
1307       _height="${BASH_REMATCH[1]}"
 
1308       _width="${BASH_REMATCH[2]}"
 
1309       xpns_msg_debug "window height: $_height, width: $_width"
 
1310       printf "%s\\n" "$_height $_width"
 
1318 xpns_check_cell_size_bulk() {
 
1319   local _cell_num="$1"
 
1321   local _bulk_cols="$1"
 
1323   local _win_height="$1"
 
1325   local _win_width="$1"
 
1327   local _ignore_flag="$1"
 
1330   # shellcheck disable=SC2178
 
1334   IFS="," read -r -a _all_cols <<< "${_bulk_cols}"
 
1335   _rows="${#_all_cols[@]}"
 
1336   for i in "${_all_cols[@]}"; do
 
1337     ((i >= _cols))   && ((_cols = i))
 
1338     ((_sum_cell = _sum_cell + i))
 
1340   if ((_sum_cell != _cell_num)); then
 
1341     xpns_msg_error "Number of cols does not equals to the number of arguments."
 
1342     xpns_msg_error "Expected (# of args) : $_cell_num, Actual (--bulk-cols) : $_sum_cell)."
 
1343     return ${XP_ELAYOUT:-6}
 
1345   local cell_height=$(((_win_height - _rows + 1) / _rows))
 
1346   local cell_width=$(((_win_width - _cols + 1) / _cols))
 
1348   ## Display basic information
 
1349   xpns_msg_debug "Window: { Height: $_win_height, Width: $_win_width }"
 
1350   xpns_msg_debug "Cell: { Height: $cell_height, Width: $cell_width }"
 
1351   xpns_msg_debug "# Of Panes: ${_cell_num}"
 
1352   xpns_msg_debug "         | Row[0] --...--> Row[MAX]"
 
1353   xpns_msg_debug "    -----+------------------------..."
 
1354   xpns_msg_debug "    Col[]| ${_all_cols[*]}"
 
1355   xpns_msg_debug "    -----+------------------------..."
 
1357   if [[ "$_ignore_flag" -ne 1 ]] && ( ((cell_height < 2))   || ((cell_width < 2))   ); then
 
1358     xpns_msg_error "Expected pane size is too small (height: $cell_height lines, width: $cell_width chars)"
 
1359     return ${XP_ESMLPANE:-7}
 
1361   printf "%s\\n" "${_cols} ${_rows} ${_all_cols[*]}"
 
1364 xpns_check_cell_size() {
 
1365   local _cell_num="$1"
 
1371   local _win_height="$1"
 
1373   local _win_width="$1"
 
1375   local _ignore_flag="$1"
 
1377   local _all_cols_num=
 
1380   if [[ -n "${_cols-}" ]] && [[ -n "${_rows-}" ]]; then
 
1381     xpns_msg_warning "Both col size and row size are provided. Col size is preferentially going to be applied."
 
1383   ## if col is only defined
 
1384   if [[ -n "${_cols-}" ]]; then
 
1385     read -r _cols _rows < <(xpns_adjust_col_row "${_cols-}" 0 "${_cell_num}")
 
1386     IFS=" " read -r -a _all_rows <<< "$(xpns_divide_equally "${_cell_num}" "${_cols}")"
 
1387     _all_cols_num="$(xpns_nums_transpose "${_all_rows[@]}")"
 
1389   ## if row is only defined
 
1390   elif [[ -n "${_rows-}" ]]; then
 
1391     read -r _cols _rows < <(xpns_adjust_col_row 0 "${_rows-}" "${_cell_num}")
 
1392     _all_cols_num="$(xpns_divide_equally "${_cell_num}" "${_rows}")"
 
1394   ## if both are undefined
 
1396     read -r _cols _rows < <(xpns_adjust_col_row 0 0 "${_cell_num}")
 
1397     _all_cols_num="$(xpns_divide_equally "${_cell_num}" "${_rows}")"
 
1400   local cell_height=$(((_win_height - _rows + 1) / _rows))
 
1401   local cell_width=$(((_win_width - _cols + 1) / _cols))
 
1403   ## Display basic information
 
1404   xpns_msg_debug "Window: { Height: $_win_height, Width: $_win_width }"
 
1405   xpns_msg_debug "Cell: { Height: $cell_height, Width: $cell_width }"
 
1406   xpns_msg_debug "# Of Panes: ${_cell_num}"
 
1407   xpns_msg_debug "         | Row[0] --...--> Row[MAX]"
 
1408   xpns_msg_debug "    -----+------------------------..."
 
1409   xpns_msg_debug "    Col[]| ${_all_cols_num}"
 
1410   xpns_msg_debug "    -----+------------------------..."
 
1412   if [[ "$_ignore_flag" -ne 1 ]] && ( ((cell_height < 2))   || ((cell_width < 2))   ); then
 
1413     xpns_msg_error "Expected pane size is too small (height: $cell_height lines, width: $cell_width chars)"
 
1414     return "${XP_ESMLPANE:-7}"
 
1416   printf "%s\\n" "${_cols} ${_rows} ${_all_cols_num}"
 
1419 # Execute from Normal mode1
 
1420 xpns_pre_execution() {
 
1424   if [[ ${XP_OPT_EXTRA} -eq 1 ]]; then
 
1425     xpns_msg_error "'-x' must be used within the running tmux session."
 
1429   # Run as best effort.
 
1430   # Because after the tmux session is created, cols and rows would be provided by tmux.
 
1431   IFS=" " read -r XP_WINDOW_HEIGHT XP_WINDOW_WIDTH < <(xpns_get_window_height_width) && {
 
1432     local _arg_num="${#XP_ARGS[@]}"
 
1433     local _cell_num _tmp_col_row_cols _tmp_cols
 
1434     if [[ -n "$XP_MAX_PANE_ARGS" ]] && ((XP_MAX_PANE_ARGS > 1)); then
 
1435       _cell_num=$((_arg_num / XP_MAX_PANE_ARGS))
 
1437       _cell_num="${_arg_num}"
 
1439     if [[ -n "${XP_OPT_BULK_COLS}" ]]; then
 
1440       _tmp_col_row_cols="$(xpns_check_cell_size_bulk \
 
1442         "${XP_OPT_BULK_COLS}" \
 
1443         "${XP_WINDOW_HEIGHT}" \
 
1444         "${XP_WINDOW_WIDTH}" \
 
1445         "${XP_OPT_IGNORE_SIZE_LIMIT:-0}")"
 
1446       local _exit_status="$?"
 
1447       [[ $_exit_status -eq ${XP_ELAYOUT} ]] && exit ${XP_ELAYOUT}
 
1448       [[ $_exit_status -eq ${XP_ESMLPANE} ]] && exit ${XP_ESMLPANE}
 
1450       _tmp_col_row_cols="$(xpns_check_cell_size \
 
1452         "${XP_OPT_CUSTOM_SIZE_COLS-}" \
 
1453         "${XP_OPT_CUSTOM_SIZE_ROWS-}" \
 
1454         "${XP_WINDOW_HEIGHT}" \
 
1455         "${XP_WINDOW_WIDTH}" \
 
1456         "${XP_OPT_IGNORE_SIZE_LIMIT:-0}")"
 
1457       [[ $? -eq ${XP_ESMLPANE} ]] && exit ${XP_ESMLPANE}
 
1460     IFS=" " read -r XP_OPT_CUSTOM_SIZE_COLS XP_OPT_CUSTOM_SIZE_ROWS _tmp_cols <<< "$_tmp_col_row_cols"
 
1461     IFS=" " read -r -a XP_COLS <<< "${_tmp_cols}"
 
1462     IFS=" " read -r -a XP_COLS_OFFSETS <<< "$(printf "%s\\n" "${XP_COLS[*]}" | xpns_nums_accumulate_sum)"
 
1463     xpns_msg_debug "Options: $(xpns_arr2args "${XP_OPTIONS[@]}")"
 
1464     xpns_msg_debug "Arguments: $(xpns_arr2args "${XP_ARGS[@]}")"
 
1468   # Because any arguments may have `-`
 
1469   if [[ ${XP_NO_OPT} -eq 1 ]]; then
 
1470     XP_ARGS=("--" "${XP_ARGS[@]}")
 
1473   # If there is any options, escape them.
 
1474   if [[ -n "${XP_OPTIONS[*]-}" ]]; then
 
1475     _opts4args=$(xpns_arr2args "${XP_OPTIONS[@]}")
 
1477   _args4args=$(xpns_arr2args "${XP_ARGS[@]}")
 
1479   # Run as best effort
 
1480   xpns_clean_session || true
 
1482   # Create new session.
 
1483   ${TMUX_XPANES_EXEC} -S "${XP_SOCKET_PATH}" new-session \
 
1484     -s "${XP_SESSION_NAME}" \
 
1485     -n "${XP_TMP_WIN_NAME}" \
 
1486     -d "${XP_ABS_THIS_FILE_NAME} ${_opts4args} ${_args4args}"
 
1488   # Avoid attaching (for unit testing).
 
1489   if [[ ${XP_OPT_ATTACH} -eq 1 ]]; then
 
1490     if ! ${TMUX_XPANES_EXEC} -S "${XP_SOCKET_PATH}" attach-session -t "${XP_SESSION_NAME}" &&
 
1491       [[ ${XP_IS_PIPE_MODE} -eq 1    ]]; then
 
1492       ## In recovery case, overwrite trap to keep socket file
 
1493       trap 'rm -f "${XP_CACHE_HOME}"/__xpns_*$$;' EXIT
 
1495       xpns_msg "Recovery" \
 
1496         "Execute below command line to re-attach the new session.
 
1498 ${TMUX_XPANES_EXEC} -S ${XP_SOCKET_PATH} attach-session -t ${XP_SESSION_NAME}
 
1506 # Execute from inside of tmux session
 
1508   local _pane_base_index=
 
1510   local _last_args_idx=
 
1511   local _def_allow_rename=
 
1514   if [[ ${XP_IS_PIPE_MODE} -eq 0 ]] && [[ -n "${XP_MAX_PANE_ARGS-}" ]]; then
 
1515     xpns_merge_array_elements "${XP_MAX_PANE_ARGS}" 'XP_ARGS'
 
1518   ## Fix window size and define pane size
 
1520     local  _tmp_col_row_cols _tmp_cols
 
1521     IFS=" " read -r XP_WINDOW_HEIGHT XP_WINDOW_WIDTH < \
 
1522       <(${TMUX_XPANES_EXEC} display-message -p '#{window_height} #{window_width}')
 
1523     if [[ -n "${XP_OPT_BULK_COLS}" ]]; then
 
1524       _tmp_col_row_cols="$(xpns_check_cell_size_bulk \
 
1526         "${XP_OPT_BULK_COLS}" \
 
1527         "${XP_WINDOW_HEIGHT}" \
 
1528         "${XP_WINDOW_WIDTH}" \
 
1529         "${XP_OPT_IGNORE_SIZE_LIMIT:-0}")"
 
1530       local _exit_status="$?"
 
1531       [[ $_exit_status -eq ${XP_ELAYOUT} ]] && exit ${XP_ELAYOUT}
 
1532       [[ $_exit_status -eq ${XP_ESMLPANE} ]] && exit ${XP_ESMLPANE}
 
1534       _tmp_col_row_cols="$(xpns_check_cell_size \
 
1536         "${XP_OPT_CUSTOM_SIZE_COLS-}" \
 
1537         "${XP_OPT_CUSTOM_SIZE_ROWS-}" \
 
1538         "${XP_WINDOW_HEIGHT}" \
 
1539         "${XP_WINDOW_WIDTH}" \
 
1540         "${XP_OPT_IGNORE_SIZE_LIMIT:-0}")"
 
1541       [[ $? -eq ${XP_ESMLPANE} ]] && exit ${XP_ESMLPANE}
 
1543     IFS=" " read -r XP_OPT_CUSTOM_SIZE_COLS XP_OPT_CUSTOM_SIZE_ROWS _tmp_cols <<< "$_tmp_col_row_cols"
 
1544     IFS=" " read -r -a XP_COLS <<< "${_tmp_cols}"
 
1545     IFS=" " read -r -a XP_COLS_OFFSETS <<< "$(printf "%s\\n" "${XP_COLS[*]}" | xpns_nums_accumulate_sum)"
 
1546     xpns_msg_debug "Options: $(xpns_arr2args "${XP_OPTIONS[@]}")"
 
1547     xpns_msg_debug "Arguments: $(xpns_arr2args "${XP_ARGS[@]}")"
 
1550   _pane_base_index=$(xpns_get_global_tmux_conf 'pane-base-index')
 
1551   _last_args_idx=$((${#XP_ARGS[@]} - 1))
 
1552   _def_allow_rename="$(xpns_get_global_tmux_conf 'allow-rename')"
 
1554   xpns_suppress_allow_rename "${_def_allow_rename-}"
 
1555   XP_CMD_UTILITY="$(xpns_get_joined_begin_commands "${XP_CMD_UTILITY}")"
 
1557   if [[ ${XP_OPT_EXTRA} -eq 1 ]]; then
 
1558     # Reuse existing window name
 
1559     # tmux 1.6 does not support -F option
 
1560     _window_name="$( ${TMUX_XPANES_EXEC} display -p -F "#{window_id}")"
 
1561     _pane_count="$( ${TMUX_XPANES_EXEC} list-panes | grep -c .)"
 
1562     _pane_base_index=$((_pane_base_index + _pane_count - 1))
 
1563     _pane_active_pane_id=$(${TMUX_XPANES_EXEC} display -p -F "#{pane_id}")
 
1566       xpns_generate_window_name \
 
1573   ## --------------------
 
1574   # Prepare window and panes
 
1575   ## --------------------
 
1576   if [[ ${XP_OPT_EXTRA} -eq 1 ]]; then
 
1577     xpns_prepare_extra_panes \
 
1579       "${_pane_base_index}" \
 
1580       "${XP_OPT_LOG_STORE}" \
 
1581       "${XP_OPT_SET_TITLE}" \
 
1582       "${XP_OPT_SPEEDY}" \
 
1583       "${XP_OPT_SPEEDY_AWAIT}" \
 
1585       "${XP_CMD_UTILITY}" \
 
1587   elif [[ ${XP_OPT_USE_PRESET_LAYOUT} -eq 1 ]]; then
 
1588     xpns_prepare_preset_layout_window \
 
1590       "${_pane_base_index}" \
 
1591       "${XP_OPT_LOG_STORE}" \
 
1592       "${XP_OPT_SET_TITLE}" \
 
1593       "${XP_OPT_ATTACH}" \
 
1594       "${XP_OPT_SPEEDY}" \
 
1595       "${XP_OPT_SPEEDY_AWAIT}" \
 
1597       "${XP_CMD_UTILITY}" \
 
1599   elif [[ ${XP_OPT_USE_PRESET_LAYOUT} -eq 0 ]]; then
 
1600     xpns_prepare_window \
 
1602       "${XP_OPT_LOG_STORE}" \
 
1603       "${XP_OPT_SET_TITLE}" \
 
1604       "${XP_OPT_ATTACH}" \
 
1605       "${XP_OPT_SPEEDY}" \
 
1606       "${XP_OPT_SPEEDY_AWAIT}" \
 
1608       "${XP_CMD_UTILITY}" \
 
1612   ## With -ss option, it is possible to close all the panes as of here.
 
1613   ## Check status of the window. If no window exists, there is nothing to do any more and just exit.
 
1614   xpns_msg_debug "xpns_is_window_alive:1: After window separation"
 
1615   xpns_is_window_alive "${_window_name}" "${XP_OPT_SPEEDY_AWAIT}" "${_def_allow_rename-}"
 
1617   if [[ ${XP_OPT_EXTRA} -eq 1 ]]; then
 
1618     # Set offset to avoid sending command to the original pane.
 
1619     _pane_base_index=$((_pane_base_index + 1))
 
1620     # Avoid to make layout even-horizontal even if there are many panes.
 
1621     # in xpns_organize_panes
 
1622     _last_args_idx=$((_last_args_idx + _pane_count))
 
1623     # Re-select the windown that was active before.
 
1624     ${TMUX_XPANES_EXEC} select-pane -t "${_window_name}.${_pane_active_pane_id}"
 
1627   if [[ ${XP_OPT_LOG_STORE} -eq 1 ]]; then
 
1628     xpns_enable_logging \
 
1630       "${_pane_base_index}" \
 
1631       "${TMUX_XPANES_LOG_DIRECTORY}" \
 
1632       "${TMUX_XPANES_LOG_FORMAT}" \
 
1636     if [[ $XP_OPT_SPEEDY -eq 1 ]]; then
 
1637       xpns_notify_logging \
 
1643   xpns_msg_debug "xpns_is_window_alive:2: After logging"
 
1644   xpns_is_window_alive "${_window_name}" "${XP_OPT_SPEEDY_AWAIT}" "${_def_allow_rename-}"
 
1646   # Set pane titles for each pane.
 
1647   if xpns_is_pane_title_required "${XP_OPT_SET_TITLE}" "${XP_OPT_EXTRA}"; then
 
1650       "${_pane_base_index}" \
 
1654   if [[ $XP_OPT_SPEEDY -eq 1 ]]; then
 
1660   xpns_msg_debug "xpns_is_window_alive:3: After setting title"
 
1661   xpns_is_window_alive "${_window_name}" "${XP_OPT_SPEEDY_AWAIT}" "${_def_allow_rename-}"
 
1663   # Sending operations for each pane.
 
1664   # With -s option, command is already sent.
 
1665   if [[ $XP_OPT_SPEEDY -eq 0 ]]; then
 
1666     xpns_send_commands \
 
1668       "${_pane_base_index}" \
 
1670       "${XP_CMD_UTILITY}" \
 
1674   xpns_msg_debug "xpns_is_window_alive:4: After sending commands"
 
1675   xpns_is_window_alive "${_window_name}" "${XP_OPT_SPEEDY_AWAIT}" "${_def_allow_rename-}"
 
1677   ## With -l <layout>, panes are organized.
 
1678   ## As well as -x, they are re-organized.
 
1679   if [[ $XP_OPT_USE_PRESET_LAYOUT -eq 1 ]] || [[ ${XP_OPT_EXTRA} -eq 1 ]]; then
 
1680     xpns_organize_panes \
 
1685   # Enable broadcasting
 
1686   if [[ ${XP_OPT_IS_SYNC} -eq 1 ]] && [[ ${XP_OPT_EXTRA} -eq 0 ]]; then
 
1687     ${TMUX_XPANES_EXEC} \
 
1688       set-window-option -t "${_window_name}" \
 
1689       synchronize-panes on
 
1692   ## In case of -t option
 
1693   if [[ ${XP_OPT_SET_TITLE} -eq 1 ]] && [[ ${XP_OPT_CHANGE_BORDER} -eq 1 ]]; then
 
1695     ${TMUX_XPANES_EXEC} \
 
1696       set-window-option -t "${_window_name}" \
 
1697       pane-border-format "${TMUX_XPANES_PANE_BORDER_FORMAT}"
 
1698     # Show border status
 
1699     ${TMUX_XPANES_EXEC} \
 
1700       set-window-option -t "${_window_name}" \
 
1701       pane-border-status "${TMUX_XPANES_PANE_BORDER_STATUS}"
 
1704   # In case of -x, this statement is skipped to keep the original window name
 
1705   if [[ ${XP_OPT_EXTRA} -eq 0 ]]; then
 
1706     # Restore original window name.
 
1707     ${TMUX_XPANES_EXEC} \
 
1708       rename-window -t "${_window_name}" \
 
1709       -- "$(printf "%s\\n" "${_window_name}" | xpns_key2value)"
 
1712   xpns_restore_allow_rename "${_def_allow_rename-}"
 
1716 # Arrange options for pipe mode
 
1717 #  * argument -> command
 
1718 #  * stdin -> argument
 
1720 xpns_switch_pipe_mode() {
 
1721   local _pane_num4new_term=""
 
1722   if [[ -n "${XP_ARGS[*]-}" ]] && [[ -n "${XP_CMD_UTILITY-}" ]]; then
 
1723     xpns_msg_error "Both arguments and other options (like '-c', '-e') which updates <command> are given."
 
1727   if [[ -z "${TMUX-}" ]]; then
 
1728     xpns_msg_warning "Attached session is required for 'Pipe mode'."
 
1729     # This condition is used when the following situations.
 
1730     #   * Enter from outside of tmux session(Normal mode1)
 
1735     #     (Normal mode1)$ echo {a..g} | ./xpanes -n 2
 
1736     # => This will once create the new window like this.
 
1737     #     (inside of tmux session)$ ./xpanes '-n' '2' 'a' 'b' 'c' 'd' 'e' 'f' 'g'
 
1738     #     => After the window is closed, following panes would be left.
 
1739     #     (pane 1)$ echo a b
 
1740     #     (pane 2)$ echo c d
 
1741     #     (pane 3)$ echo e f
 
1743     # In order to create such the query,
 
1744     # separate all the argument into minimum tokens
 
1746     if [[ -n "${XP_MAX_PANE_ARGS-}" ]]; then
 
1747       _pane_num4new_term=1
 
1751   while read -r line; do
 
1752     XP_STDIN+=("${line}")
 
1753   done < <(cat | xpns_rm_empty_line |
 
1754     xpns_pipe_filter "${_pane_num4new_term:-${XP_MAX_PANE_ARGS}}")
 
1756   # Merge them into command.
 
1757   if [[ -n "${XP_ARGS[*]-}" ]]; then
 
1758     # Attention: It might be wrong result if IFS is changed.
 
1759     XP_CMD_UTILITY="${XP_ARGS[*]}"
 
1762   # If there is empty -I option or user does not assign the <repstr>,
 
1763   # Append the space and <repstr> at the end of the <command>
 
1764   # This is same as the xargs command of GNU.
 
1766   #   $ echo 10 | xargs seq
 
1769   #   $ echo 10 | xargs -I@ seq @
 
1771   if [[ -z "${XP_REPSTR}" ]]; then
 
1772     XP_REPSTR="${XP_DEFAULT_REPSTR}"
 
1773     if [[ -n "${XP_ARGS[*]-}" ]]; then
 
1774       XP_CMD_UTILITY="${XP_ARGS[*]-} ${XP_REPSTR}"
 
1778   # Deal with stdin as arguments.
 
1779   XP_ARGS=("${XP_STDIN[@]-}")
 
1782 xpns_layout_short2long() {
 
1785     -e 's/^eh$/even-horizontal/' \
 
1786     -e 's/^ev$/even-vertical/' \
 
1787     -e 's/^mh$/main-horizontal/' \
 
1788     -e 's/^mv$/main-vertical/' \
 
1792 xpns_is_valid_layout() {
 
1793   local _layout="${1-}"
 
1794   local _pat='^(tiled|even-horizontal|even-vertical|main-horizontal|main-vertical)$'
 
1795   if ! [[ $_layout =~ $_pat ]]; then
 
1796     xpns_msg_error "Invalid layout '${_layout}'."
 
1801 xpns_warning_before_extra() {
 
1803   local _synchronized=
 
1804   _synchronized="$(xpns_get_local_tmux_conf "synchronize-panes")"
 
1805   if [[ "on" == "${_synchronized}" ]]; then
 
1806     xpns_msg_warning "Panes are now synchronized.
 
1807 '-x' option may cause unexpected behavior on the synchronized panes."
 
1808     printf "Are you really sure? [y/n]: "
 
1810     if ! [[ "${_ans-}" =~ ^[yY]  ]]; then
 
1816 xpns_opt_check_num() {
 
1820   if [[ -n "$_arg" ]] && [[ -z "${_arg//[0-9]/}" ]]; then
 
1823   xpns_msg_error "Invalid argument '$_arg' for $_option option"
 
1827 xpns_opt_check_str() {
 
1831   if [[ -n "$_arg" ]]; then
 
1834   xpns_msg_error "Invalid argument '$_arg' for $_option option"
 
1838 xpns_opt_check_num_csv() {
 
1842   if [[ "$1" =~ ^([0-9][0-9]*,?)+$ ]]; then
 
1845   xpns_msg_error "Invalid argument '$_arg' for $_option option"
 
1849 xpns_parse_options() {
 
1850   while (($# > 0)); do
 
1853     if [[ ${XP_NO_OPT} -eq 1 ]]; then
 
1858     ## Skip regularization if the arg is empty or --log= option
 
1859     if [[ -n "$opt" ]] && [[ -n "${opt##--log=*}" ]]; then
 
1860       ## -ovalue → -o value
 
1861       if [[ -z "${opt##-${XP_ARG_OPTIONS}?*}" ]]; then
 
1862         set -- "${opt#??}" ${1+"$@"}
 
1865       elif [[ -z "${opt##-[!-]?*}" ]]; then
 
1866         set -- "-${opt#??}" ${1+"$@"}
 
1868       ## --option=value → --option value
 
1869       elif [[ -z "${opt##--*=*}" ]]; then
 
1870         set -- "${opt#--*=}" ${1+"$@"}
 
1877         # Disable any more options
 
1892         XP_OPTIONS+=("$opt")
 
1896         xpns_opt_check_str "$opt" "$1"
 
1897         XP_OPTIONS+=("$opt")
 
1899         TMUX_XPANES_LOG_FORMAT="$1"
 
1905         XP_OPTIONS+=("$opt")
 
1906         TMUX_XPANES_LOG_DIRECTORY="${opt#--log=}"
 
1907         xpns_opt_check_str "${opt%=}" "$TMUX_XPANES_LOG_DIRECTORY"
 
1911         XP_OPTIONS+=("$opt")
 
1914         XP_OPTIONS+=("$opt")
 
1915         XP_CMD_UTILITY="${XP_SSH_CMD_UTILITY}"
 
1918         XP_OPT_CHANGE_BORDER=1
 
1921         XP_OPT_SPEEDY_AWAIT=1
 
1924         XP_OPTIONS+=("$opt")
 
1928         xpns_opt_check_num "$opt" "$1"
 
1929         XP_OPTIONS+=("$opt")
 
1930         XP_OPT_CUSTOM_SIZE_COLS="$1"
 
1935         xpns_opt_check_num "$opt" "$1"
 
1936         XP_OPTIONS+=("$opt")
 
1937         XP_OPT_CUSTOM_SIZE_ROWS="$1"
 
1942         xpns_opt_check_num_csv "$opt" "$1"
 
1943         XP_OPTIONS+=("$opt")
 
1944         XP_OPT_BULK_COLS="$1"
 
1949         XP_OPTIONS+=("$opt")
 
1953         XP_OPTIONS+=("$opt")
 
1956       --ignore-size-limit)
 
1957         XP_OPTIONS+=("$opt")
 
1958         XP_OPT_IGNORE_SIZE_LIMIT=1
 
1961       # Short options without argument
 
1972         XP_OPTIONS+=("$opt")
 
1974         XP_OPT_USE_PRESET_LAYOUT=1 ## Layout presets must be used with -x
 
1975         if ! xpns_warning_before_extra; then
 
1980         XP_OPTIONS+=("$opt")
 
1984         XP_OPTIONS+=("$opt")
 
1989         XP_OPTIONS+=("$opt")
 
1990         if ( xpns_tmux_is_greater_equals 2.3 ); then
 
1992           XP_OPT_CHANGE_BORDER=1
 
1994           xpns_msg_warning "-t option cannot be used by tmux version less than 2.3. Disabled."
 
1999         XP_OPTIONS+=("$opt")
 
2001         XP_OPT_SPEEDY_AWAIT=1
 
2002         if [[ -z "${1#-s}" ]]; then
 
2003           XP_OPT_SPEEDY_AWAIT=0
 
2009       # Short options with argument
 
2012         xpns_opt_check_str "$opt" "$1"
 
2013         XP_OPTIONS+=("$opt")
 
2019         xpns_opt_check_str "$opt" "$1"
 
2020         XP_OPTIONS+=("$opt")
 
2021         XP_OPT_USE_PRESET_LAYOUT=1
 
2022         XP_LAYOUT="$(printf '%s\n' "$1" | xpns_layout_short2long)"
 
2023         xpns_is_valid_layout "${XP_LAYOUT}"
 
2029         # xpns_opt_check_str "$opt" "$1"
 
2030         XP_OPTIONS+=("$opt")
 
2032         XP_OPT_CMD_UTILITY=1
 
2037         xpns_opt_check_num "$opt" "$1"
 
2038         XP_OPTIONS+=("$opt")
 
2039         XP_MAX_PANE_ARGS="$1"
 
2044         xpns_opt_check_str "$opt" "$1"
 
2045         XP_OPTIONS+=("$opt")
 
2051         xpns_opt_check_num "$opt" "$1"
 
2052         XP_OPTIONS+=("$opt")
 
2053         XP_OPT_CUSTOM_SIZE_COLS="$1"
 
2058         xpns_opt_check_num "$opt" "$1"
 
2059         XP_OPTIONS+=("$opt")
 
2060         XP_OPT_CUSTOM_SIZE_ROWS="$1"
 
2066         # xpns_opt_check_str "$opt" "$1"
 
2067         XP_OPTIONS+=("$opt")
 
2068         XP_BEGIN_ARGS+=("$1")
 
2073         xpns_msg_error "Invalid option -- '${opt}'"
 
2086   # If there is any standard input from pipe,
 
2087   # 1 line handled as 1 argument.
 
2088   if [[ ! -t 0 ]]; then
 
2090     xpns_switch_pipe_mode
 
2093   # When no argument is given, exit.
 
2094   if [[ -z "${XP_ARGS[*]-}" ]]; then
 
2095     xpns_msg_error "No arguments."
 
2100   if [[ -n "${XP_OPT_CUSTOM_SIZE_COLS-}" ]] || [[ -n "${XP_OPT_CUSTOM_SIZE_ROWS-}" ]]; then
 
2101     if [[ "$XP_OPT_EXTRA" -eq 1 ]]; then
 
2102       xpns_msg_warning "The columns/rows options (-C, --cols, -R, --rows) cannot be used with -x option. Ignored."
 
2103     elif [[ "$XP_OPT_EXTRA" -eq 0 ]] && [[ "${XP_OPT_USE_PRESET_LAYOUT}" -eq 1 ]]; then
 
2104       # This part is required to keep backward compatibility.
 
2105       ## Users can simulate xpanes v3.x to set : alias xpanes="xpanes -lt"
 
2106       xpns_msg_info "Columns/rows option (-C, --cols, -R, --rows) and -l option are provided. Disable -l. "
 
2107       XP_OPT_USE_PRESET_LAYOUT=0
 
2111   # Set default value in case of empty.
 
2112   XP_CMD_UTILITY="${XP_CMD_UTILITY:-${XP_DEFAULT_CMD_UTILITY}}"
 
2113   XP_REPSTR="${XP_REPSTR:-${XP_DEFAULT_REPSTR}}"
 
2115   # To set command on pre_execution, set -c option manually.
 
2116   if [[ ${XP_OPT_CMD_UTILITY} -eq 0 ]]; then
 
2117     XP_OPTIONS+=("-c" "${XP_CMD_UTILITY}")
 
2121 ## --------------------------------
 
2123 ## --------------------------------
 
2125   xpns_parse_options ${1+"$@"}
 
2126   xpns_check_env "${XP_DEPENDENCIES}"
 
2127   ## --------------------------------
 
2128   # Parameter validation
 
2129   ## --------------------------------
 
2130   # When do dry-run flag is enabled, skip running (this is used to execute unit test of itself).
 
2131   if [[ ${XP_OPT_DRY_RUN} -eq 1 ]]; then
 
2134   # Validate log directory.
 
2135   if [[ ${XP_OPT_LOG_STORE} -eq 1 ]]; then
 
2136     TMUX_XPANES_LOG_DIRECTORY=$(xpns_normalize_directory "${TMUX_XPANES_LOG_DIRECTORY}")
 
2137     xpns_is_valid_directory "${TMUX_XPANES_LOG_DIRECTORY}" &&
 
2138       TMUX_XPANES_LOG_DIRECTORY=$(cd "${TMUX_XPANES_LOG_DIRECTORY}" && pwd)
 
2140   ## --------------------------------
 
2141   # If current shell is outside of tmux session.
 
2142   ## --------------------------------
 
2143   if [[ -z "${TMUX-}" ]]; then
 
2145   ## --------------------------------
 
2146   # If current shell is already inside of tmux session.
 
2147   ## --------------------------------
 
2154 ## --------------------------------
 
2156 ## --------------------------------