madduck's git repository

Every one of the projects in this repository is available at the canonical URL git://git.madduck.net/madduck/pub/<projectpath> — see each project's metadata for the exact URL.

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.

SSH access, as well as push access can be individually arranged.

If you use my repositories frequently, consider adding the following snippet to ~/.gitconfig and using the third clone URL listed for each project:

[url "git://git.madduck.net/madduck/"]
  insteadOf = madduck:

add xpanes master
authormartin f. krafft <madduck@madduck.net>
Fri, 1 May 2020 00:11:53 +0000 (12:11 +1200)
committermartin f. krafft <madduck@madduck.net>
Fri, 1 May 2020 00:12:42 +0000 (12:12 +1200)
.bin/xpanes [new file with mode: 0755]
.gitignore.d/tmux

diff --git a/.bin/xpanes b/.bin/xpanes
new file mode 100755 (executable)
index 0000000..5b3d1f9
--- /dev/null
@@ -0,0 +1,2057 @@
+#!/usr/bin/env bash
+readonly XP_SHELL="/usr/bin/env bash"
+
+# @Author Yamada, Yasuhiro
+# @Filename xpanes
+
+set -u
+readonly XP_VERSION="4.1.1"
+
+## trap might be updated in 'xpns_pre_execution' function
+trap 'rm -f "${XP_CACHE_HOME}"/__xpns_*$$; rm -f "${XP_DEFAULT_SOCKET_PATH}"' EXIT
+
+## --------------------------------
+# Error constants
+## --------------------------------
+# Undefined or General errors
+readonly XP_EUNDEF=1
+
+# Invalid option/argument
+readonly XP_EINVAL=4
+
+# Could not open tty.
+readonly XP_ETTY=5
+
+# Invalid layout.
+readonly XP_ELAYOUT=6
+
+# Impossible layout: Small pane
+readonly XP_ESMLPANE=7
+
+# Log related exit status is 2x.
+## Could not create a directory.
+readonly XP_ELOGDIR=20
+
+## Could not directory to store logs is not writable.
+readonly XP_ELOGWRITE=21
+
+# User's intentional exit is 3x
+## User exit the process intentionally by following warning message.
+readonly XP_EINTENT=30
+
+## All the panes are closed before processing due to user's options/command.
+readonly XP_ENOPANE=31
+
+# Necessary commands are not found
+readonly XP_ENOCMD=127
+
+# ===============
+
+# XP_THIS_FILE_NAME is supposed to be "xpanes".
+readonly XP_THIS_FILE_NAME="${0##*/}"
+readonly XP_THIS_DIR="$(cd "$(dirname "${BASH_SOURCE[0]:-${(%):-%N}}")" && pwd)"
+readonly XP_ABS_THIS_FILE_NAME="${XP_THIS_DIR}/${XP_THIS_FILE_NAME}"
+
+# Prevent cache directory being created under root / directory in any case.
+# This is quite rare case (but it can be happened).
+readonly XP_USER_HOME="${HOME:-/tmp}"
+
+# Basically xpanes follows XDG Base Direcotry Specification.
+# https://specifications.freedesktop.org/basedir-spec/basedir-spec-0.6.html
+XDG_CACHE_HOME="${XDG_CACHE_HOME:-${XP_USER_HOME}/.cache}"
+readonly XP_CACHE_HOME="${XDG_CACHE_HOME}/xpanes"
+
+# This is supposed to be xpanes-12345(PID)
+readonly XP_SESSION_NAME="${XP_THIS_FILE_NAME}-$$"
+# Temporary window name is tmp-12345(PID)
+readonly XP_TMP_WIN_NAME="tmp-$$"
+readonly XP_EMPTY_STR="EMPTY"
+
+readonly XP_SUPPORT_TMUX_VERSION_LOWER="1.8"
+
+# Check dependencies just in case.
+# Even POSIX compliant commands are only used in this program.
+# `xargs`, `sleep`, `mkfifo` are omitted because minimum functions can work without them.
+readonly XP_DEPENDENCIES="${XP_DEPENDENCIES:-tmux grep sed tr od echo touch printf cat sort pwd cd mkfifo}"
+
+## --------------------------------
+# User customizable shell variables
+## --------------------------------
+TMUX_XPANES_EXEC=${TMUX_XPANES_EXEC:-tmux}
+TMUX_XPANES_PANE_BORDER_FORMAT="${TMUX_XPANES_PANE_BORDER_FORMAT:-#[bg=green,fg=black] #T #[default]}"
+TMUX_XPANES_PANE_BORDER_STATUS="${TMUX_XPANES_PANE_BORDER_STATUS:-bottom}"
+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'}
+XP_DEFAULT_TMUX_XPANES_LOG_FORMAT="[:ARG:].log.%Y-%m-%d_%H-%M-%S"
+TMUX_XPANES_LOG_FORMAT="${TMUX_XPANES_LOG_FORMAT:-${XP_DEFAULT_TMUX_XPANES_LOG_FORMAT}}"
+XP_DEFAULT_TMUX_XPANES_LOG_DIRECTORY="${XP_CACHE_HOME}/logs"
+TMUX_XPANES_LOG_DIRECTORY="${TMUX_XPANES_LOG_DIRECTORY:-${XP_DEFAULT_TMUX_XPANES_LOG_DIRECTORY}}"
+
+## --------------------------------
+# Initialize Options
+## --------------------------------
+# options which work individually.
+readonly XP_FLAG_OPTIONS="[hVdetxs]"
+# options which need arguments.
+readonly XP_ARG_OPTIONS="[ISclnCRB]"
+readonly XP_DEFAULT_LAYOUT="tiled"
+readonly XP_DEFAULT_REPSTR="{}"
+readonly XP_DEFAULT_CMD_UTILITY="echo {} "
+readonly XP_SSH_CMD_UTILITY="ssh -o StrictHostKeyChecking=no {} "
+XP_OPTIONS=()
+XP_ARGS=()
+XP_STDIN=()
+XP_BEGIN_ARGS=()
+XP_IS_PIPE_MODE=0
+XP_OPT_IS_SYNC=1
+XP_OPT_DRY_RUN=0
+XP_OPT_ATTACH=1
+XP_OPT_LOG_STORE=0
+XP_REPSTR=""
+XP_DEFAULT_SOCKET_PATH_BASE="${XP_CACHE_HOME}/socket"
+XP_DEFAULT_SOCKET_PATH="${XP_DEFAULT_SOCKET_PATH_BASE}.$$"
+XP_SOCKET_PATH="${XP_SOCKET_PATH:-${XP_DEFAULT_SOCKET_PATH}}"
+XP_NO_OPT=0
+XP_OPT_CMD_UTILITY=0
+XP_CMD_UTILITY=""
+XP_LAYOUT="${XP_DEFAULT_LAYOUT}"
+XP_MAX_PANE_ARGS=""
+XP_OPT_SET_TITLE=0
+XP_OPT_CHANGE_BORDER=0
+XP_OPT_EXTRA=0
+XP_OPT_SPEEDY=0
+XP_OPT_SPEEDY_AWAIT=0
+XP_OPT_USE_PRESET_LAYOUT=0
+XP_OPT_CUSTOM_SIZE_COLS=
+XP_OPT_CUSTOM_SIZE_ROWS=
+XP_OPT_BULK_COLS=
+XP_WINDOW_WIDTH=
+XP_WINDOW_HEIGHT=
+XP_COLS=
+XP_COLS_OFFSETS=
+XP_OPT_DEBUG=0
+XP_OPT_IGNORE_SIZE_LIMIT=0
+
+## --------------------------------
+# Logger
+#   $1 -- Log level (i.e Warning, Error)
+#   $2 -- Message
+#   i.e
+#      xpanes:Error: invalid option.
+#
+# This log format is created with reference to openssl's one.
+#   $ echo | openssl -a
+#   openssl:Error: '-a' is an invalid command.
+## --------------------------------
+xpns_msg() {
+  local _loglevel="$1"
+  local _msgbody="$2"
+  local _msg="${XP_THIS_FILE_NAME}:${_loglevel}: ${_msgbody}"
+  printf "%s\\n" "${_msg}" >&2
+}
+
+xpns_msg_info() {
+  xpns_msg "Info" "$1"
+}
+
+xpns_msg_warning() {
+  xpns_msg "Warning" "$1"
+}
+
+xpns_msg_debug() {
+  if [[ $XP_OPT_DEBUG -eq 1 ]];then
+    xpns_msg "Debug" "$(date "+[%F_%T]"):${FUNCNAME[1]}:$1"
+  fi
+}
+
+xpns_msg_error() {
+  xpns_msg "Error" "$1"
+}
+
+xpns_usage_warn() {
+  xpns_usage_short >&2
+  echo "Try '${XP_THIS_FILE_NAME} --help' for more information." >&2
+}
+
+xpns_usage_short() {
+  cat << _EOS_
+Usage: ${XP_THIS_FILE_NAME} [OPTIONS] [argument ...]
+Usage(Pipe mode): command ... | ${XP_THIS_FILE_NAME} [OPTIONS] [<command> ...]
+_EOS_
+}
+
+xpns_usage() {
+  cat <<USAGE
+Usage:
+  ${XP_THIS_FILE_NAME} [OPTIONS] [argument ...]
+
+Usage(Pipe mode):
+  command ... | ${XP_THIS_FILE_NAME} [OPTIONS] [<command> ...]
+
+OPTIONS:
+  -h,--help                    Display this help and exit.
+  -V,--version                 Output version information and exit.
+  -B <begin-command>           Run <begin-command> before processing <command> in each pane. Multiple options are allowed.
+  -c <command>                 Set <command> to be executed in each pane. Default is \`echo {}\`.
+  -d,--desync                  Make synchronize-panes option off in new window.
+  -e                           Execute given arguments as is. Same as \`-c '{}'\`
+  -I <repstr>                  Replacing one or more occurrences of <repstr> in command provided by -c or -B. Default is \`{}\`.
+  -C NUM,--cols=NUM            Number of columns of window layout.
+  -R NUM,--rows=NUM            Number of rows of window layout.
+  -l <layout>                  Set the preset of window layout. Recognized layout arguments are:
+                               t    tiled
+                               eh   even-horizontal
+                               ev   even-vertical
+                               mh   main-horizontal
+                               mv   main-vertical
+  -n <number>                  Set the maximum number of <argument> taken for each pane.
+  -s                           Speedy mode: Run command without opening an interactive shell.
+  -ss                          Speedy mode AND close a pane automatically at the same time as process exiting.
+  -S <socket-path>             Set a full alternative path to the server socket.
+  -t                           Display each argument on the each pane's border as their title.
+  -x                           Create extra panes in the current active window.
+  --log[=<directory>]          Enable logging and store log files to ~/.cache/xpanes/logs or <directory>.
+  --log-format=<FORMAT>        Make name of log files follow <FORMAT>. Default is \`${XP_DEFAULT_TMUX_XPANES_LOG_FORMAT}\`.
+  --ssh                        Same as \`-t -s -c 'ssh -o StrictHostKeyChecking=no {}'\`.
+  --stay                       Do not switch to new window.
+  --bulk-cols=NUM1[,NUM2 ...]  Set number of columns on multiple rows (i.e, "2,2,2" represents 2 cols x 3 rows).
+  --debug                      Print debug message.
+
+Copyright (c) 2019 Yamada, Yasuhiro
+Released under the MIT License.
+https://github.com/greymd/tmux-xpanes
+USAGE
+}
+
+# Show version number
+xpns_version() {
+  echo "${XP_THIS_FILE_NAME} ${XP_VERSION}"
+}
+
+# Get version number for tmux
+xpns_get_tmux_version() {
+  local _tmux_version=""
+  if ! ${TMUX_XPANES_EXEC} -V &> /dev/null; then
+    # From tmux 0.9 to 1.3, there is no -V option.
+    _tmux_version="tmux 0.9-1.3"
+  else
+    _tmux_version="$( ${TMUX_XPANES_EXEC} -V )"
+  fi
+  ( read -r _ _ver; echo "${_ver}" ) <<<"${_tmux_version}"
+}
+
+# Check whether the prefered tmux version is greater than host's tmux version.
+# $1 ... Prefered version.
+# $2 ... Host tmux version(optional).
+# In case of tmux version is 1.7, the result will be like this.
+# 0 is true, 1 is false.
+##  arg  -> result
+#   func 1.5  1.7 -> 0
+#   func 1.6  1.7 -> 0
+#   func 1.7  1.7 -> 0
+#   func 1.8  1.7 -> 1
+#   func 1.9  1.7 -> 1
+#   func 1.9a 1.7 -> 1
+#   func 2.0  1.7 -> 1
+xpns_tmux_is_greater_equals() {
+  local _check_version="$1"
+  local _tmux_version="${2:-$(xpns_get_tmux_version)}"
+  # Simple numerical comparison does not work because there is the version like "1.9a".
+  if [[ "$( (echo "${_tmux_version}"; echo "${_check_version}") | sort -n | head -n 1)" != "${_check_version}" ]];then
+    return 1
+  else
+    return 0
+  fi
+}
+
+xpns_get_local_tmux_conf() {
+  local _conf_name="$1"
+  local _session="${2-}"
+  {
+    if [[ -z "${_session-}" ]];then
+      ${TMUX_XPANES_EXEC} show-window-options
+    else
+      ${TMUX_XPANES_EXEC} -S "${_session}" show-window-options
+    fi
+  } | grep "^${_conf_name}" \
+    | ( read -r _ _v; printf "%s\\n" "${_v}" )
+}
+
+xpns_get_global_tmux_conf() {
+  local _conf_name="$1"
+  local _session="${2-}"
+  {
+    if [[ -z "${_session-}" ]];then
+      ${TMUX_XPANES_EXEC} show-window-options -g
+    else
+      ${TMUX_XPANES_EXEC} -S "${_session}" show-window-options -g
+    fi
+  } | grep "^${_conf_name}" \
+    | ( read -r _ _v; printf "%s\\n" "${_v}" )
+}
+
+# Disable allow-rename because
+# window separation does not work correctly
+# if "allow-rename" option is on
+xpns_suppress_allow_rename () {
+  local _default_allow_rename="$1"
+  local _session="${2-}"
+  if [[ "${_default_allow_rename-}" == "on"  ]]; then
+    ## Temporary, disable "allow-rename"
+    xpns_msg_debug "'allow-rename' option is 'off' temporarily."
+    if [[ -z "${_session-}" ]];then
+      ${TMUX_XPANES_EXEC} set-window-option -g allow-rename off
+    else
+      ${TMUX_XPANES_EXEC} -S "${_session}" set-window-option -g allow-rename off
+    fi
+  fi
+}
+
+# Restore default "allow-rename"
+# Do not write like 'xpns_restore_allow_rename "some value" "some value" > /dev/null'
+# In tmux 1.6, 'tmux set-window-option' might be stopped in case of redirection.
+xpns_restore_allow_rename () {
+  local _default_allow_rename="$1"
+  local _session="${2-}"
+  if [[ "${_default_allow_rename-}" == "on"  ]]; then
+    xpns_msg_debug "Restore original value of 'allow-rename' option."
+    if [[ -z "${_session-}" ]];then
+      ${TMUX_XPANES_EXEC} set-window-option -g allow-rename on
+    else
+      ${TMUX_XPANES_EXEC} -S "${_session}" set-window-option -g allow-rename on
+    fi
+  fi
+}
+
+# func "11" "2"
+#  => 6
+# 11 / 2 = 5.5 => ceiling => 6
+xpns_ceiling () {
+  local _divide="$1";shift
+  local _by="$1"
+  printf "%s\\n" $(( ( _divide + _by - 1 ) / _by ))
+}
+
+# func "10" "3"
+#  => 4 3 3
+# Divide 10 into 3 parts as equally as possible.
+xpns_divide_equally () {
+  local _number="$1";shift
+  local _count="$1"
+  local _upper _lower _upper_count _lower_count
+  _upper="$(xpns_ceiling "$_number" "$_count")"
+  _lower=$(( _upper - 1 ))
+  _lower_count=$(( _upper * _count - _number ))
+  _upper_count=$(( _count - _lower_count ))
+  eval "printf '${_upper} %.0s' {1..$_upper_count}"
+  (( _lower_count > 0 )) && eval "printf '${_lower} %.0s' {1..$_lower_count}"
+}
+
+# echo 3 3 3 3 | func
+# => 3 6 9 12
+xpns_nums_accumulate_sum () {
+  local s=0
+  while read -r n; do
+    ((s = s + n ))
+    printf "%s " "$s"
+  done < <( cat | tr ' ' '\n')
+}
+
+# func 3 2 2 2
+# => 4 4 1
+#
+# For example, "3 2 2 2" represents following cell positions
+#   1  2  3
+# 1 [] [] [] => 3 rows
+# 2 [] []    => 2 rows
+# 3 [] []    => 2 rows
+# 4 [] []    => 2 rows
+#
+# After the transposition, it must be "4 4 1" which represents below
+#   1  2  3  4
+# 1 [] [] [] [] => 4 rows
+# 2 [] [] [] [] => 4 rows
+# 3 []          => 1 rows
+xpns_nums_transpose () {
+  local _colnum="$1"
+  local _spaces=
+  local _result=
+  xpns_msg_debug "column num = $_colnum, input = $*"
+  _spaces="$(for i in "$@";do
+    printf "%${i}s\\n"
+  done)"
+
+  # 'for' statement does not work somehow
+  _result="$(while read -r i; do
+    ## This part is depending on the following 'cut' behavior
+    ## $ echo 1234 | cut -c 5
+    ## => result is supposed to be empty
+    printf "%s\\n" "$_spaces" | cut -c "$i" | grep -c ' '
+  done < <(xpns_seq 1 "${_colnum}") | xargs)"
+  xpns_msg_debug "result = $_result"
+  printf "%s\\n" "$_result"
+}
+
+# Adjust size of columns and rows in accordance with given N
+# func <col> <row> <N>
+# i.e:
+#     func "" "" 20
+#       => returns 4 5
+#     func "6" 0 20
+#       => returns 6 4
+xpns_adjust_col_row() {
+  local col="${1:-0}" ;shift
+  local row="${1:-0}" ;shift
+  local N="$1"   ;shift
+  local fix_col_flg
+  local fix_row_flg
+  (( col != 0 )) && fix_col_flg=1 || fix_col_flg=0
+  (( row != 0 )) && fix_row_flg=1 || fix_row_flg=0
+
+  # This is just a author (@greymd)'s preference.
+  if (( fix_col_flg == 0 )) && (( fix_row_flg == 0 )) && (( N == 2)) ;then
+    col=2
+    row=1
+    printf "%d %d\\n" "${col}" "${row}"
+    return
+  fi
+
+  # If both valures are provided, col is used.
+  if (( fix_col_flg == 1 )) && (( fix_row_flg == 1 ));then
+    row=0
+    fix_row_flg=0
+  fi
+  # This algorhythm is almost same as tmux default
+  #   https://github.com/tmux/tmux/blob/2.8/layout-set.c#L436
+  while (( col * row < N )) ;do
+    (( fix_row_flg != 1 )) && (( row = row + 1 ))
+    if (( col * row < N ));then
+      (( fix_col_flg != 1 )) &&  (( col = col + 1 ))
+    fi
+  done
+  printf "%d %d\\n" "${col}" "${row}"
+}
+
+# Make each line unique by adding index number
+# echo aaa bbb ccc aaa ccc ccc | xargs -n 1 | xpns_unique_line
+#  aaa-1
+#  bbb-1
+#  ccc-1
+#  aaa-2
+#  ccc-2
+#  ccc-3
+#
+# Eval is used because associative array is not supported before bash 4.2
+xpns_unique_line () {
+  local _val_name
+  while read -r line; do
+    _val_name="__xpns_hash_$(printf "%s" "${line}" | xpns_value2key)"
+    # initialize variable
+    eval "${_val_name}=\${${_val_name}:-0}"
+    # increment variable
+    eval "${_val_name}=\$(( ++${_val_name} ))"
+    printf "%s\\n" "${line}-$(eval printf "%s" "\$${_val_name}")"
+  done
+}
+
+#
+# Generate log file names from given arguments.
+# Usage:
+#        echo <arg1> <arg2> ... | xpns_log_filenames <FORMAT>
+# Return:
+#        File names.
+# Example:
+#        $ echo aaa bbb ccc aaa ccc ccc | xargs -n 1 | xpns_log_filenames '[:ARG:]_[:PID:]_%Y%m%d.log'
+#        aaa-1_1234_20160101.log
+#        bbb-1_1234_20160101.log
+#        ccc-1_1234_20160101.log
+#        aaa-2_1234_20160101.log
+#        ccc-2_1234_20160101.log
+#        ccc-3_1234_20160101.log
+#
+xpns_log_filenames () {
+  local _arg_fmt="$1"
+  local _full_fmt=
+  _full_fmt="$(date "+${_arg_fmt}")"
+  cat \
+    | \
+    # 1st argument + '-' + unique number (avoid same argument has same name)
+    xpns_unique_line \
+    | while read -r _arg
+    do
+      cat <<<"${_full_fmt}" \
+        | sed "s/\\[:ARG:\\]/${_arg}/g" \
+        | sed "s/\\[:PID:\\]/$$/g"
+    done
+}
+
+## --------------------------------
+# Normalize directory by making following conversion.
+#  * Tilde expansion.
+#  * Remove the slash '/' at the end of the dirname.
+# Usage:
+#        xpns_normalize_directory <direname>
+# Return:
+#        Normalized <dirname>
+## --------------------------------
+xpns_normalize_directory() {
+  local _dir="$1"
+  # Remove end of slash '/'
+  _dir="${_dir%/}"
+  # tilde expansion
+  _dir="${_dir/#~/${HOME}}"
+  printf "%s\\n" "${_dir}"
+}
+
+## --------------------------------
+# Ensure existence of given directory
+# Usage:
+#        xpns_is_valid_directory <direname>
+# Return:
+#        Absolute path of the <dirname>
+## --------------------------------
+xpns_is_valid_directory() {
+  local _dir="$1"
+  local _checkfile="${XP_THIS_FILE_NAME}.$$"
+  # Check directory.
+  if [[ ! -d "${_dir}" ]]; then
+    # Create directory
+    if mkdir "${_dir}"; then
+      xpns_msg_info "${_dir} is created."
+    else
+      xpns_msg_error "Failed to create ${_dir}"
+      exit ${XP_ELOGDIR}
+    fi
+  fi
+  # Try to create file.
+  #   Not only checking directory permission,
+  #   but also i-node and other misc situations.
+  if ! touch "${_dir}/${_checkfile}"; then
+    xpns_msg_error "${_dir} is not writable."
+    rm -f "${_dir}/${_checkfile}"
+    exit ${XP_ELOGWRITE}
+  fi
+  rm -f "${_dir}/${_checkfile}"
+}
+
+# Convert array to string which is can be used as command line argument.
+# Usage:
+#       xpns_arr2args <array object>
+# Example:
+#       array=(aaa bbb "ccc ddd" eee "f'f")
+#       xpns_arr2args "${array[@]}"
+#       @returns "'aaa' 'bbb' 'ccc ddd' 'eee' 'f\'f'"
+# Result:
+xpns_arr2args() {
+  local _arg=""
+  # If there is no argument, usage will be shown.
+  if [[ $# -lt 1 ]]; then
+    return 0
+  fi
+  for i in "$@" ;do
+    _arg="${i}"
+    # Use 'cat <<<"input"' command instead of 'echo',
+    # because such the command recognizes option like '-e'.
+    cat <<<"${_arg}" \
+      | \
+      # Escaping single quotations.
+      sed "s/'/'\"'\"'/g" \
+      | \
+      # Surround argument with single quotations.
+      sed "s/^/'/;s/$/' /" \
+      | \
+      # Remove new lines
+      tr -d '\n'
+  done
+}
+
+# Extract first field to generate window name.
+# ex, $2     =  'aaa bbb ccc'
+#   return   =  aaa-12345(PID)
+xpns_generate_window_name() {
+  local _unprintable_str="${1-}"; shift
+  # Leave first 200 characters to prevent
+  # the name exceed the maximum length of tmux window name (2000 byte).
+  printf "%s\\n" "${1:-${_unprintable_str}}" \
+    | ( read -r _name _ && printf "%s\\n" "${_name:0:200}-$$" )
+}
+
+# Convert string to another string which can be handled as tmux window name.
+xpns_value2key() {
+  od -v -tx1 -An  | tr -dc 'a-zA-Z0-9' | tr -d '\n'
+}
+
+# Restore string encoded by xpns_value2key function.
+xpns_key2value() {
+  read -r _key
+  # shellcheck disable=SC2059
+  printf "$(printf "%s" "$_key" | sed 's/../\\x&/g')"
+}
+
+# Remove empty lines
+# This function behaves like `awk NF`
+xpns_rm_empty_line() {
+  { cat; printf "\\n";} | while IFS= read -r line;do
+    # shellcheck disable=SC2086
+    set -- ${line-}
+    if [[ $# != 0 ]]; then
+      printf "%s\\n" "${line}"
+    fi
+  done
+}
+
+# Extract matched patterns from string
+# $ xpns_extract_matched "aaa123bbb" "[0-9]{3}"
+# => "123"
+xpns_extract_matched() {
+  local _args="$1" ;shift
+  local _regex="($1)"
+  if [[ $_args =~ $_regex ]];then
+    printf "%s" "${BASH_REMATCH[0]}"
+  fi
+}
+
+# Enable logging feature to the all the panes in the target window.
+xpns_enable_logging() {
+  local _window_name="$1"     ; shift
+  local _index_offset="$1"    ; shift
+  local _log_dir="$1"         ; shift
+  local _log_format="$1"      ; shift
+  local _unprintable_str="$1" ; shift
+  local _args=("$@")
+  local _args_num=$(($# - 1))
+  # Generate log files from arguments.
+  local _idx=0
+  while read -r _logfile ; do
+    # Start logging
+    xpns_msg_debug "Start logging pipe-pane(cat >> '${_log_dir}/${_logfile}')"
+    ${TMUX_XPANES_EXEC} \
+      pipe-pane -t "${_window_name}.$(( _idx + _index_offset ))" \
+      "cat >> '${_log_dir}/${_logfile}'" # Tilde expansion does not work here.
+    _idx=$(( _idx + 1 ))
+  done < <(
+  for i in $(xpns_seq 0 "${_args_num}")
+  do
+    # Replace empty string.
+    printf "%s\\n" "${_args[i]:-${_unprintable_str}}"
+  done | xpns_log_filenames "${_log_format}"
+  )
+}
+
+## Print "1" on the particular named pipe
+xpns_notify() {
+  local _wait_id="$1" ; shift
+  local _fifo=
+  _fifo="${XP_CACHE_HOME}/__xpns_${_wait_id}"
+  xpns_msg_debug "Notify to $_fifo"
+  printf "%s\\n" 1 > "$_fifo" &
+}
+
+xpns_notify_logging() {
+  local _window_name="$1" ; shift
+  local _args_num=$(($# - 1))
+  for i in $(xpns_seq 0 "${_args_num}"); do
+    xpns_notify "log_${_window_name}-${i}-$$"
+  done
+}
+
+xpns_notify_sync() {
+  local _window_name="$1" ; shift
+  local _args_num=$(($# - 1))
+  for i in $(xpns_seq 0 "${_args_num}"); do
+    xpns_notify "sync_${_window_name}-${i}-$$" &
+  done
+}
+
+xpns_is_window_alive() {
+  local _window_name="$1" ;shift
+  local _speedy_await_flag="$1" ;shift
+  local _def_allow_rename="$1" ;shift
+  if ! ${TMUX_XPANES_EXEC} display-message -t "$_window_name" -p > /dev/null 2>&1 ;then
+    xpns_msg_info "All the panes are closed before displaying the result."
+    if [[ "${_speedy_await_flag}" -eq 0 ]] ;then
+      xpns_msg_info "Use '-s' option instead of '-ss' option to avoid this behavior."
+    fi
+    xpns_restore_allow_rename "${_def_allow_rename-}"
+    exit ${XP_ENOPANE}
+  fi
+}
+
+xpns_inject_title() {
+  local _target_pane="$1" ;shift
+  local _message="$1"     ;shift
+  local _pane_tty=
+  _pane_tty="$( ${TMUX_XPANES_EXEC} display-message -t "${_target_pane}" -p "#{pane_tty}" )"
+  printf "\\033]2;%s\\033\\\\" "${_message}" > "${_pane_tty}"
+  xpns_msg_debug "target_pane=${_target_pane} pane_title=${_message} pane_tty=${_pane_tty}"
+}
+
+xpns_is_pane_title_required() {
+  local _title_flag="$1"   ; shift
+  local _extra_flag="$1"   ; shift
+  local _pane_border_status=
+  _pane_border_status=$(xpns_get_local_tmux_conf "pane-border-status")
+  if [[ $_title_flag -eq 1 ]]; then
+    return 0
+  elif [[ ${_extra_flag} -eq 1 ]] && \
+       [[ "${_pane_border_status}" != "off" ]] && \
+       [[ -n "${_pane_border_status}" ]] ;then
+    ## For -x option
+    # Even the -t option is not specified, it is required to inject pane title here.
+    # Because user expects the title is displayed on the pane if the original window is
+    # generated from tmux-xpanes with -t option.
+    return 0
+  fi
+  return 1
+}
+
+# Set pane titles for each pane for -t option
+xpns_set_titles() {
+  local _window_name="$1"  ; shift
+  local _index_offset="$1" ; shift
+  local _index=0
+  local _pane_index=
+  for arg in "$@"
+  do
+    _pane_index=$(( _index + _index_offset ))
+    xpns_inject_title "${_window_name}.${_pane_index}" "${arg}"
+    _index=$(( _index + 1 ))
+  done
+}
+
+# Send command to the all the panes in the target window.
+xpns_send_commands() {
+  local _window_name="$1"  ; shift
+  local _index_offset="$1" ; shift
+  local _repstr="$1"       ; shift
+  local _cmd="$1"          ; shift
+  local _index=0
+  local _pane_index=
+  local _exec_cmd=
+  for arg in "$@"
+  do
+    _exec_cmd="${_cmd//${_repstr}/${arg}}"
+    _pane_index=$(( _index + _index_offset ))
+    ${TMUX_XPANES_EXEC} send-keys -t "${_window_name}.${_pane_index}" "${_exec_cmd}" C-m
+    _index=$(( _index + 1 ))
+  done
+}
+
+# Separate window vertically, when the number of panes is 1 or 2.
+xpns_organize_panes() {
+  local _window_name="$1" ; shift
+  local _args_num="$1"
+  ## ----------------
+  # Default behavior
+  ## ----------------
+  if [[ "${_args_num}" -eq 1 ]]; then
+    ${TMUX_XPANES_EXEC} select-layout -t "${_window_name}" even-horizontal
+  elif [[ "${_args_num}" -gt 1 ]]; then
+    ${TMUX_XPANES_EXEC} select-layout -t "${_window_name}" tiled
+  fi
+  ## ----------------
+  # Update layout
+  ## ----------------
+  if [[ "${XP_LAYOUT}" != "${XP_DEFAULT_LAYOUT}" ]]; then
+    ${TMUX_XPANES_EXEC} select-layout -t "${_window_name}" "${XP_LAYOUT}"
+  fi
+}
+
+#
+# Generate sequential number descending order.
+# seq is not used because old version of
+# seq does not generate descending oorder.
+# $ xpns_seq 3 0
+# 3
+# 2
+# 1
+# 0
+#
+xpns_seq () {
+  local _num1="$1"
+  local _num2="$2"
+  eval "printf \"%d\\n\" {$_num1..$_num2}"
+}
+
+xpns_wait_func() {
+  local _wait_id="$1"
+  local _fifo="${XP_CACHE_HOME}/__xpns_${_wait_id}"
+  local _arr=("$_fifo")
+  local _fifo_arg=
+  _fifo_arg=$(xpns_arr2args "${_arr[@]}")
+  xpns_msg_debug "mkfifo $_fifo"
+  mkfifo "${_fifo}"
+  xpns_msg_debug "grep -q 1 ${_fifo_arg}"
+  printf "%s\\n" "grep -q 1 ${_fifo_arg}"
+}
+
+# Split a new window into multiple panes.
+#
+xpns_split_window() {
+  local _window_name="$1"     ; shift
+  local _log_flag="$1"        ; shift
+  local _title_flag="$1"      ; shift
+  local _speedy_flag="$1"     ; shift
+  local _await_flag="$1"      ; shift
+  local _pane_base_index="$1" ; shift
+  local _repstr="$1"          ; shift
+  local _cmd_template="$1"    ; shift
+  local _exec_cmd=
+  local _sep_count=0
+  local args=("$@")
+  _last_idx=$(( ${#args[@]} - 1 ))
+
+  for i in $(xpns_seq $_last_idx 0)
+  do
+    xpns_msg_debug "Index:${i} Argument:${args[i]}"
+    _sep_count=$((_sep_count + 1))
+    _exec_cmd="${_cmd_template//${_repstr}/${args[i]}}"
+
+    ## Speedy mode
+    if [[ $_speedy_flag -eq 1 ]]; then
+
+      _exec_cmd=$(xpns_inject_wait_command "${_log_flag}" "${_title_flag}" "${_speedy_flag}" "${_await_flag}" "$i" "${_exec_cmd}")
+      # Execute command as a child process of default-shell.
+      ${TMUX_XPANES_EXEC} split-window -t "${_window_name}" -h -d "${_exec_cmd}"
+    else
+      # Open login shell and execute command on the interactive screen.
+      ${TMUX_XPANES_EXEC} split-window -t "${_window_name}" -h -d
+    fi
+    # Restraining that size of pane's width becomes
+    # less than the minimum size which is defined by tmux.
+    if [[ ${_sep_count} -gt 2 ]]; then
+      ${TMUX_XPANES_EXEC} select-layout -t "${_window_name}" tiled
+    fi
+  done
+}
+
+#
+# Create new panes to the  existing window.
+# Usage:
+#    func <window name> <offset of index> <number of pane>
+#
+xpns_prepare_extra_panes() {
+  local _window_name="$1"     ; shift
+  local _pane_base_index="$1" ; shift
+  local _log_flag="$1"        ; shift
+  local _title_flag="$1"      ; shift
+  local _speedy_flg="$1"      ; shift
+  local _await_flg="$1"       ; shift
+  # specify a pane which has the biggest index number.
+  #   Because pane_id may not be immutable.
+  #   If the small number of index is specified here, correspondance between pane_title and command can be slip off.
+  ${TMUX_XPANES_EXEC} select-pane -t "${_window_name}.${_pane_base_index}"
+
+  # split window into multiple panes
+  xpns_split_window \
+    "${_window_name}" \
+    "${_log_flag}" \
+    "${_title_flag}" \
+    "${_speedy_flg}" \
+    "${_await_flg}" \
+    "${_pane_base_index}" \
+    "$@"
+}
+
+xpns_get_joined_begin_commands () {
+  local _commands="$1"
+  if [[ "${#XP_BEGIN_ARGS[*]}" -lt 1 ]]; then
+    printf "%s" "${_commands}"
+    return
+  fi
+  printf "%s\\n" "${XP_BEGIN_ARGS[@]}" "${_commands}"
+}
+
+xpns_inject_wait_command () {
+  local _log_flag="$1"    ; shift
+  local _title_flag="$1"  ; shift
+  local _speedy_flg="$1"  ; shift
+  local _await_flg="$1"   ; shift
+  local _idx="$1"         ; shift
+  local _exec_cmd="$1"    ; shift
+
+  ## Speedy mode + logging
+  if [[ "${_log_flag}" -eq 1 ]] && [[ "${_speedy_flg}" -eq 1 ]]; then
+    # Wait for start of logging
+    # Without this part, logging thread may start after new process is finished.
+    # Execute function to wait for logging start.
+    _exec_cmd="$(xpns_wait_func "log_${_window_name}-${_idx}-$$")"$'\n'"${_exec_cmd}"
+  fi
+
+  ## Speedy mode (Do not allow to close panes before the separation is finished).
+  if [[ "${_speedy_flg}" -eq 1 ]]; then
+    _exec_cmd="$(xpns_wait_func "sync_${_window_name}-${_idx}-$$")"$'\n'${_exec_cmd}
+  fi
+
+  ## -s: Speedy mode (Not -ss: Speedy mode + nowait)
+  if [[ "${_await_flg}" -eq 1 ]]; then
+    local _msg
+    _msg="$(xpns_arr2args "${TMUX_XPANES_PANE_DEAD_MESSAGE}" | sed 's/"/\\"/g')"
+    _exec_cmd="${_exec_cmd}"$'\n'"${XP_SHELL} -c \"printf -- ${_msg} >&2 && read\""
+  fi
+  printf "%s" "${_exec_cmd}"
+}
+
+xpns_new_window () {
+  local _window_name="$1" ; shift
+  local _attach_flg="$1"  ; shift
+  local _speedy_flg="$1"  ; shift
+  local _exec_cmd="$1"    ; shift
+  local _window_id=
+
+  # Create new window.
+  if [[ "${_attach_flg}" -eq 1 ]]; then
+    if [[ "${_speedy_flg}" -eq 1 ]]; then
+      _window_id=$(${TMUX_XPANES_EXEC} new-window -n "${_window_name}" -F '#{window_id}' -P "${_exec_cmd}")
+    else
+      _window_id=$(${TMUX_XPANES_EXEC} new-window -n "${_window_name}" -F '#{window_id}' -P )
+    fi
+  else
+    # Keep background
+    if [[ "${_speedy_flg}" -eq 1 ]]; then
+      _window_id=$(${TMUX_XPANES_EXEC} new-window -n "${_window_name}" -F '#{window_id}' -P -d "${_exec_cmd}")
+    else
+      _window_id=$(${TMUX_XPANES_EXEC} new-window -n "${_window_name}" -F '#{window_id}' -P -d)
+    fi
+  fi
+  printf "%s" "${_window_id}"
+}
+
+xpns_new_pane_vertical () {
+  local _window_id="$1"   ; shift
+  local _cell_height="$1" ; shift
+  local _speedy_flg="$1"  ; shift
+  local _exec_cmd="$1"    ; shift
+  local _pane_id=
+  if [[ "${_speedy_flg}" -eq 1 ]]; then
+    _pane_id="$(${TMUX_XPANES_EXEC} split-window -t "$_window_id" -v -d -l "${_cell_height}" -F '#{pane_id}' -P "${_exec_cmd}")"
+  else
+    _pane_id="$(${TMUX_XPANES_EXEC} split-window -t "$_window_id" -v -d -l "${_cell_height}" -F '#{pane_id}' -P)"
+  fi
+  printf "%s\\n" "${_pane_id}"
+}
+
+xpns_split_pane_horizontal () {
+  local _target_pane_id="$1" ; shift
+  local _cell_width="$1"     ; shift
+  local _speedy_flg="$1"     ; shift
+  local _exec_cmd="$1"       ; shift
+  if [[ "${_speedy_flg}" -eq 1 ]]; then
+    ${TMUX_XPANES_EXEC} split-window -t "$_target_pane_id" -h -d -l "$_cell_width" "${_exec_cmd}"
+  else
+    ${TMUX_XPANES_EXEC} split-window -t "$_target_pane_id" -h -d -l "$_cell_width"
+  fi
+}
+
+xpns_prepare_window () {
+  local _window_name="$1"     ; shift
+  local _log_flag="$1"        ; shift
+  local _title_flag="$1"      ; shift
+  local _attach_flg="$1"      ; shift
+  local _speedy_flg="$1"      ; shift
+  local _await_flg="$1"       ; shift
+  local _repstr="$1"          ; shift
+  local _cmd_template="$1"    ; shift
+  local _args=("$@")
+  local _window_height="$XP_WINDOW_HEIGHT"
+  local _window_width="$XP_WINDOW_WIDTH"
+  local _col="$XP_OPT_CUSTOM_SIZE_COLS"
+  local _row="$XP_OPT_CUSTOM_SIZE_ROWS"
+  local _cols=("${XP_COLS[@]}")
+  local _cols_offset=("${XP_COLS_OFFSETS[@]}")
+  local _exec_cmd=
+  local _pane_id=
+  local _first_pane_id=
+  local _window_id=
+  local _cell_height=
+  local _cell_width=
+  local _top_pane_height=
+  local _current_pane_width=
+  local i=
+  local j=
+  local _rest_col=
+  local _rest_row=
+  local _offset=
+
+  _cell_height=$(( ( _window_height - _row + 1 ) / _row ))
+  ## Insert first element
+  _exec_cmd="${_cmd_template//${_repstr}/${_args[0]}}"
+  _exec_cmd="$(xpns_inject_wait_command "${_log_flag}" "${_title_flag}" "${_speedy_flg}" "${_await_flg}" 0 "${_exec_cmd}")"
+  _window_id=$(xpns_new_window "${_window_name}" "${_attach_flg}" "${_speedy_flg}" "${_exec_cmd}")
+  _first_pane_id=$(${TMUX_XPANES_EXEC} display-message -t "$_window_id" -p -F '#{pane_id}' | head -n 1)
+
+  ## Start from last row
+  for (( i = _row - 1 ; i > 0 ; i-- ));do
+    _col="${_cols[i]}"
+    _cell_width=$(( ( _window_width - _col + 1 ) / _col ))
+    xpns_msg_debug "_col=$_col"
+    (( _offset = _cols_offset[i] ))
+    for (( j = 0 ; j < _col ; j++ ));do
+      if (( j == 0 )) ;then
+        (( idx = _offset - _col ))
+        # Create new row
+        # Insert first element of the row first
+        _exec_cmd="${_cmd_template//${_repstr}/${_args[idx]}}"
+        _exec_cmd="$(xpns_inject_wait_command "${_log_flag}" "${_title_flag}" "${_speedy_flg}" "${_await_flg}" "${idx}" "${_exec_cmd}")"
+        _pane_id=$(xpns_new_pane_vertical "${_window_name}" "${_cell_height}" "${_speedy_flg}" "${_exec_cmd}")
+      fi
+      # Separate row into columns
+      if (( j != 0 )) ;then
+        (( idx = _offset - j ))
+        _exec_cmd="${_cmd_template//${_repstr}/${_args[idx]}}"
+        _exec_cmd="$(xpns_inject_wait_command "${_log_flag}" "${_title_flag}" "${_speedy_flg}" "${_await_flg}" "${idx}" "${_exec_cmd}")"
+        ## Separate row into columns
+        _current_pane_width=$(${TMUX_XPANES_EXEC} display-message -t "$_pane_id" -p '#{pane_width}' | head -n 1)
+        _rest_col=$(( _col - j + 1 ))
+        _cell_width=$(( ( _current_pane_width - _rest_col + 1 ) / _rest_col ))
+        xpns_split_pane_horizontal "$_pane_id" "$_cell_width" "${_speedy_flg}" "${_exec_cmd}"
+      fi
+    done
+
+    # Adjust height
+    _top_pane_height=$(${TMUX_XPANES_EXEC} display-message -t "$_window_id" -p '#{pane_height}' | head -n 1)
+    _rest_row=$(( i ))
+    xpns_msg_debug "_top_pane_height=$_top_pane_height _rest_row=$_rest_row"
+    _cell_height=$(( ( _top_pane_height - _rest_row + 1 ) / _rest_row ))
+  done
+
+  # Split first row into columns
+  _col="${_cols[0]}"
+  _cell_width=$(( ( _window_width - _col + 1 ) / _col ))
+  for (( j = 1 ; j < _col ; j++ ));do
+    idx=$(( _cols_offset[0] - j ))
+    # Adjust width
+    _current_pane_width=$(${TMUX_XPANES_EXEC} display-message -t "$_first_pane_id" -p '#{pane_width}' | head -n 1)
+    _rest_col=$(( _col - j + 1 ))
+    _cell_width=$(( ( _current_pane_width - _rest_col + 1 ) / _rest_col ))
+    ## Split top row into columns
+    _exec_cmd="${_cmd_template//${_repstr}/${_args[idx]}}"
+    _exec_cmd="$(xpns_inject_wait_command "${_log_flag}" "${_title_flag}" "${_speedy_flg}" "${_await_flg}" "${idx}" "${_exec_cmd}")"
+    xpns_split_pane_horizontal "${_first_pane_id}" "${_cell_width}" "${_speedy_flg}" "${_exec_cmd}"
+  done
+}
+
+xpns_is_session_running() {
+  local _socket="$1"
+  ${TMUX_XPANES_EXEC} -S "${_socket}" list-session > /dev/null 2>&1
+}
+
+# Remove unnecessary session files as much as possible
+# to let xpanes avoids to load old .tmux.conf.
+xpns_clean_session() {
+  if [[ "${XP_SOCKET_PATH}" != "${XP_DEFAULT_SOCKET_PATH}" ]]; then
+    return
+  fi
+  # Delete old socket file (xpanes v3.1.0 or before).
+  if [[ -e "${XP_DEFAULT_SOCKET_PATH_BASE}" ]]; then
+    if ! xpns_is_session_running "${XP_DEFAULT_SOCKET_PATH_BASE}" ;then
+      xpns_msg_debug "socket(${XP_DEFAULT_SOCKET_PATH_BASE}) is not running. Remove it"
+      rm -f "${XP_DEFAULT_SOCKET_PATH_BASE}"
+    fi
+  fi
+  for _socket in "${XP_CACHE_HOME}"/socket.* ;do
+    xpns_msg_debug "file = ${_socket}"
+    if ! xpns_is_session_running "${_socket}" ;then
+      xpns_msg_debug "socket(${_socket}) is not running. Remove it"
+      rm -f "${_socket}"
+    else
+      xpns_msg_debug "socket(${_socket}) is running. Keep ${_socket}"
+    fi
+  done
+}
+
+#
+# Split a new window which was created by tmux into multiple panes.
+# Usage:
+#    xpns_prepare_preset_layout_window <window name> <offset of index> <number of pane> <attach or not>
+#
+xpns_prepare_preset_layout_window() {
+  local _window_name="$1"     ; shift
+  local _pane_base_index="$1" ; shift
+  local _log_flag="$1"        ; shift
+  local _title_flag="$1"      ; shift
+  local _attach_flg="$1"      ; shift
+  local _speedy_flg="$1"      ; shift
+  local _await_flg="$1"       ; shift
+  # Create new window.
+  if [[ "${_attach_flg}" -eq 1 ]]; then
+    ${TMUX_XPANES_EXEC} new-window -n "${_window_name}"
+  else
+    # Keep background
+    ${TMUX_XPANES_EXEC} new-window -n "${_window_name}" -d
+  fi
+
+  # specify a pane which has the youngest number of index.
+  ${TMUX_XPANES_EXEC} select-pane -t "${_window_name}.${_pane_base_index}"
+
+  # split window into multiple panes
+  xpns_split_window \
+    "${_window_name}" \
+    "${_log_flag}" \
+    "${_title_flag}" \
+    "${_speedy_flg}" \
+    "${_await_flg}" \
+    "${_pane_base_index}" \
+    "$@"
+
+  ### If the first pane is still remaining,
+  ### panes cannot be organized well.
+  # Delete the first pane
+  ${TMUX_XPANES_EXEC} kill-pane -t "${_window_name}.${_pane_base_index}"
+
+  # Select second pane here.
+  #   If the command gets error, it would most likely be caused by user (XP_ENOPANE).
+  #   Suppress error message here and announce it in xpns_execution.
+  ${TMUX_XPANES_EXEC} select-pane -t "${_window_name}.${_pane_base_index}" > /dev/null 2>&1
+}
+
+# Check whether given command is in the PATH or not.
+xpns_check_env() {
+  local _cmds="$1"
+  while read -r cmd ; do
+    if ! type "${cmd}" > /dev/null 2>&1; then
+      if [[ "${cmd}" == "tmux" ]] && [[ "${TMUX_XPANES_EXEC}" == "tmux" ]]; then
+        xpns_msg_error "${cmd} is required. Install ${cmd} or set TMUX_XPANES_EXEC variable."
+        exit ${XP_ENOCMD}
+      elif [[ "${cmd}" != "tmux" ]]; then
+        xpns_msg_error "${cmd} is required."
+        exit ${XP_ENOCMD}
+      fi
+    fi
+  done < <(echo "${_cmds}" | tr ' ' '\n')
+
+  if ! mkdir -p "${XP_CACHE_HOME}";then
+    xpns_msg_warning "failed to create cache directory '${XP_CACHE_HOME}'."
+  fi
+
+  # Do not omit this part, this is used by testing.
+  TMUX_XPANES_TMUX_VERSION="${TMUX_XPANES_TMUX_VERSION:-$(xpns_get_tmux_version)}"
+  if ( xpns_tmux_is_greater_equals \
+    "${XP_SUPPORT_TMUX_VERSION_LOWER}" \
+    "${TMUX_XPANES_TMUX_VERSION}" ) ;then
+    : "Supported tmux version"
+  else
+    xpns_msg_warning \
+"'${XP_THIS_FILE_NAME}' may not work correctly! Please check followings.
+* tmux is installed correctly.
+* Supported tmux version is installed.
+  Version ${XP_SUPPORT_TMUX_VERSION_LOWER} and over is officially supported."
+  fi
+
+  return 0
+}
+
+xpns_pipe_filter() {
+  local _number="${1-}"
+  if [[ -z "${_number-}" ]]; then
+    cat
+  else
+    xargs -n "${_number}"
+  fi
+}
+
+xpns_set_args_per_pane() {
+  local _pane_num="$1"; shift
+  local _filtered_args=()
+  while read -r _line; do
+    _filtered_args+=("${_line}")
+  done < <(xargs -n "${_pane_num}" <<<"$(xpns_arr2args "${XP_ARGS[@]}")")
+  XP_ARGS=("${_filtered_args[@]}")
+}
+
+xpns_get_window_height_width() {
+  local _height=
+  local _width=
+  local _result=
+  local _dev=
+  local _pattern='^([0-9]+)[ \t]+([0-9]+)$'
+
+  if ! type stty > /dev/null 2>&1; then
+    xpns_msg_debug "'stty' does not exist: Failed to get window height and size. Skip checking"
+    return 1
+  fi
+
+  ## This condition is used for unit testing
+  if [[ -z "${XP_IS_PIPE_MODE-}" ]]; then
+    if [[ ! -t 0 ]]; then
+      XP_IS_PIPE_MODE=1
+    fi
+  fi
+  if [[ $XP_IS_PIPE_MODE -eq 0 ]]; then
+    if _result=$(stty size 2> /dev/null) && [[ "$_result" =~ $_pattern ]];then
+      _height="${BASH_REMATCH[1]}"
+      _width="${BASH_REMATCH[2]}"
+      xpns_msg_debug "window height: $_height, width: $_width"
+      printf "%s\\n" "$_height $_width"
+      return 0
+    fi
+  else
+    if ! type ps > /dev/null 2>&1 ;then
+      xpns_msg_debug "'ps' does not exist: Failed to get window height and size. Skip checking"
+      return 1
+    fi
+    { read -r; read -r _dev; } < <(ps -o tty -p $$)
+    ## If it's Linux, -F option is used
+    if _result=$(stty -F "/dev/${_dev}" size 2> /dev/null) && [[ "$_result" =~ $_pattern ]];then
+      _height="${BASH_REMATCH[1]}"
+      _width="${BASH_REMATCH[2]}"
+      xpns_msg_debug "window height: $_height, width: $_width"
+      printf "%s\\n" "$_height $_width"
+      return 0
+    fi
+    ## If it's BSD, macOS, -F option is used
+    if _result=$(stty -f "/dev/${_dev}" size 2> /dev/null) && [[ "$_result" =~ $_pattern ]];then
+      _height="${BASH_REMATCH[1]}"
+      _width="${BASH_REMATCH[2]}"
+      xpns_msg_debug "window height: $_height, width: $_width"
+      printf "%s\\n" "$_height $_width"
+      return 0
+    fi
+    return 1
+  fi
+  return 1
+}
+
+xpns_check_cell_size_bulk() {
+  local _cell_num="$1"    ; shift
+  local _bulk_cols="$1"   ; shift
+  local _win_height="$1"  ; shift
+  local _win_width="$1"   ; shift
+  local _ignore_flag="$1" ; shift
+  local _all_cols=()
+  # shellcheck disable=SC2178
+  local _cols=0
+  local _rows=0
+  local _sum_cell=0
+  IFS="," read -r -a _all_cols <<< "${_bulk_cols}"
+  _rows="${#_all_cols[@]}"
+  for i in "${_all_cols[@]}"; do
+    (( i >= _cols )) && (( _cols = i ))
+    (( _sum_cell = _sum_cell + i ))
+  done
+  if (( _sum_cell != _cell_num )) ;then
+    xpns_msg_error "Number of cols does not equals to the number of arguments."
+    xpns_msg_error "Expected (# of args) : $_cell_num, Actual (--bulk-cols) : $_sum_cell)."
+    return ${XP_ELAYOUT:-6}
+  fi
+  local cell_height=$(( ( _win_height - _rows + 1 ) / _rows ))
+  local cell_width=$(( ( _win_width - _cols + 1 ) / _cols ))
+
+  ## Display basic information
+  xpns_msg_debug "Window: { Height: $_win_height, Width: $_win_width }"
+  xpns_msg_debug "Cell: { Height: $cell_height, Width: $cell_width }"
+  xpns_msg_debug "# Of Panes: ${_cell_num}"
+  xpns_msg_debug "         | Row[0] --...--> Row[MAX]"
+  xpns_msg_debug "    -----+------------------------..."
+  xpns_msg_debug "    Col[]| ${_all_cols[*]}"
+  xpns_msg_debug "    -----+------------------------..."
+
+  if [[ "$_ignore_flag" -ne 1 ]] && ( (( cell_height < 2 )) || (( cell_width < 2 )) ); then
+    xpns_msg_error "Expected pane size is too small (height: $cell_height lines, width: $cell_width chars)"
+    return ${XP_ESMLPANE:-7}
+  fi
+  printf "%s\\n" "${_cols} ${_rows} ${_all_cols[*]}"
+}
+
+xpns_check_cell_size() {
+  local _cell_num="$1"    ; shift
+  local _cols="$1"        ; shift
+  local _rows="$1"        ; shift
+  local _win_height="$1"  ; shift
+  local _win_width="$1"   ; shift
+  local _ignore_flag="$1" ; shift
+  local _all_cols_num=
+  local _all_rows=()
+
+  if [[ -n "${_cols-}" ]] && [[ -n "${_rows-}" ]];then
+    xpns_msg_warning "Both col size and row size are provided. Col size is preferentially going to be applied."
+  fi
+  ## if col is only defined
+  if [[ -n "${_cols-}" ]] ;then
+    read -r _cols _rows < <(xpns_adjust_col_row "${_cols-}" 0 "${_cell_num}")
+    IFS=" " read -r -a _all_rows <<< "$(xpns_divide_equally "${_cell_num}" "${_cols}")"
+    _all_cols_num="$(xpns_nums_transpose "${_all_rows[@]}")"
+
+  ## if row is only defined
+  elif [[ -n "${_rows-}" ]] ;then
+    read -r _cols _rows < <(xpns_adjust_col_row 0 "${_rows-}" "${_cell_num}")
+    _all_cols_num="$(xpns_divide_equally "${_cell_num}" "${_rows}")"
+
+  ## if both are undefined
+  else
+    read -r _cols _rows < <(xpns_adjust_col_row 0 0 "${_cell_num}")
+    _all_cols_num="$(xpns_divide_equally "${_cell_num}" "${_rows}")"
+  fi
+
+  local cell_height=$(( ( _win_height - _rows + 1 ) / _rows ))
+  local cell_width=$(( ( _win_width - _cols + 1 ) / _cols ))
+
+  ## Display basic information
+  xpns_msg_debug "Window: { Height: $_win_height, Width: $_win_width }"
+  xpns_msg_debug "Cell: { Height: $cell_height, Width: $cell_width }"
+  xpns_msg_debug "# Of Panes: ${_cell_num}"
+  xpns_msg_debug "         | Row[0] --...--> Row[MAX]"
+  xpns_msg_debug "    -----+------------------------..."
+  xpns_msg_debug "    Col[]| ${_all_cols_num}"
+  xpns_msg_debug "    -----+------------------------..."
+
+  if [[ "$_ignore_flag" -ne 1 ]] && ( (( cell_height < 2 )) || (( cell_width < 2 )) ); then
+    xpns_msg_error "Expected pane size is too small (height: $cell_height lines, width: $cell_width chars)"
+    return "${XP_ESMLPANE:-7}"
+  fi
+  printf "%s\\n" "${_cols} ${_rows} ${_all_cols_num}"
+}
+
+# Execute from Normal mode1
+xpns_pre_execution() {
+  local _opts4args=""
+  local _args4args=""
+
+  if [[ ${XP_OPT_EXTRA} -eq 1 ]];then
+    xpns_msg_error "'-x' must be used within the running tmux session."
+    exit ${XP_EINVAL}
+  fi
+
+  # Run as best effort.
+  # Because after the tmux session is created, cols and rows would be provided by tmux.
+  IFS=" " read -r XP_WINDOW_HEIGHT XP_WINDOW_WIDTH < <(xpns_get_window_height_width) && {
+    local _arg_num="${#XP_ARGS[@]}"
+    local _cell_num _tmp_col_row_cols _tmp_cols
+    if [[ -n "$XP_MAX_PANE_ARGS" ]] && (( XP_MAX_PANE_ARGS > 1 ));then
+      _cell_num=$(( _arg_num / XP_MAX_PANE_ARGS ))
+    else
+      _cell_num="${_arg_num}"
+    fi
+    if [[ -n "${XP_OPT_BULK_COLS}" ]]; then
+      _tmp_col_row_cols="$(xpns_check_cell_size_bulk \
+        "${_cell_num}" \
+        "${XP_OPT_BULK_COLS}" \
+        "${XP_WINDOW_HEIGHT}" \
+        "${XP_WINDOW_WIDTH}" \
+        "${XP_OPT_IGNORE_SIZE_LIMIT:-0}")"
+      local _exit_status="$?"
+      [[ $_exit_status -eq ${XP_ELAYOUT} ]] && exit ${XP_ELAYOUT}
+      [[ $_exit_status -eq ${XP_ESMLPANE} ]] && exit ${XP_ESMLPANE}
+    else
+      _tmp_col_row_cols="$(xpns_check_cell_size \
+        "${_cell_num}" \
+        "${XP_OPT_CUSTOM_SIZE_COLS-}" \
+        "${XP_OPT_CUSTOM_SIZE_ROWS-}" \
+        "${XP_WINDOW_HEIGHT}" \
+        "${XP_WINDOW_WIDTH}" \
+        "${XP_OPT_IGNORE_SIZE_LIMIT:-0}")"
+      [[ $? -eq ${XP_ESMLPANE} ]] && exit ${XP_ESMLPANE}
+    fi
+
+    IFS=" " read -r XP_OPT_CUSTOM_SIZE_COLS XP_OPT_CUSTOM_SIZE_ROWS _tmp_cols <<< "$_tmp_col_row_cols"
+    IFS=" " read -r -a XP_COLS <<< "${_tmp_cols}"
+    IFS=" " read -r -a XP_COLS_OFFSETS <<< "$(printf "%s\\n" "${XP_COLS[*]}" | xpns_nums_accumulate_sum)"
+    xpns_msg_debug "Options: $(xpns_arr2args "${XP_OPTIONS[@]}")"
+    xpns_msg_debug "Arguments: $(xpns_arr2args "${XP_ARGS[@]}")"
+  }
+
+  # Append -- flag.
+  # Because any arguments may have `-`
+  if [[ ${XP_NO_OPT} -eq 1 ]]; then
+    XP_ARGS=("--" "${XP_ARGS[@]}")
+  fi
+
+  # If there is any options, escape them.
+  if [[ -n "${XP_OPTIONS[*]-}" ]]; then
+    _opts4args=$(xpns_arr2args "${XP_OPTIONS[@]}")
+  fi
+  _args4args=$(xpns_arr2args "${XP_ARGS[@]}")
+
+  # Run as best effort
+  xpns_clean_session || true
+
+  # Create new session.
+  ${TMUX_XPANES_EXEC} -S "${XP_SOCKET_PATH}" new-session \
+    -s "${XP_SESSION_NAME}" \
+    -n "${XP_TMP_WIN_NAME}" \
+    -d "${XP_ABS_THIS_FILE_NAME} ${_opts4args} ${_args4args}"
+
+  # Avoid attaching (for unit testing).
+  if [[ ${XP_OPT_ATTACH} -eq 1 ]]; then
+    if ! ${TMUX_XPANES_EXEC} -S "${XP_SOCKET_PATH}" attach-session -t "${XP_SESSION_NAME}" \
+      && [[ ${XP_IS_PIPE_MODE} -eq 1 ]]; then
+      ## In recovery case, overwrite trap to keep socket file
+      trap 'rm -f "${XP_CACHE_HOME}"/__xpns_*$$;' EXIT
+
+      xpns_msg "Recovery" \
+"Execute below command line to re-attach the new session.
+
+${TMUX_XPANES_EXEC} -S ${XP_SOCKET_PATH} attach-session -t ${XP_SESSION_NAME}
+
+"
+      exit ${XP_ETTY}
+    fi
+  fi
+}
+
+# Execute from inside of tmux session
+xpns_execution() {
+  local _pane_base_index=
+  local _window_name=
+  local _last_args_idx=
+  local _def_allow_rename=
+  local _pane_count=0
+
+  if [[ ${XP_IS_PIPE_MODE} -eq 0 ]] && [[ -n "${XP_MAX_PANE_ARGS-}" ]];then
+    xpns_set_args_per_pane "${XP_MAX_PANE_ARGS}"
+  fi
+
+  ## Fix window size and define pane size
+  {
+    local  _tmp_col_row_cols _tmp_cols
+    IFS=" " read -r XP_WINDOW_HEIGHT XP_WINDOW_WIDTH < \
+      <(${TMUX_XPANES_EXEC} display-message -p '#{window_height} #{window_width}')
+    if [[ -n "${XP_OPT_BULK_COLS}" ]]; then
+      _tmp_col_row_cols="$(xpns_check_cell_size_bulk \
+        "${#XP_ARGS[@]}" \
+        "${XP_OPT_BULK_COLS}" \
+        "${XP_WINDOW_HEIGHT}" \
+        "${XP_WINDOW_WIDTH}" \
+        "${XP_OPT_IGNORE_SIZE_LIMIT:-0}")"
+      local _exit_status="$?"
+      [[ $_exit_status -eq ${XP_ELAYOUT} ]] && exit ${XP_ELAYOUT}
+      [[ $_exit_status -eq ${XP_ESMLPANE} ]] && exit ${XP_ESMLPANE}
+    else
+      _tmp_col_row_cols="$(xpns_check_cell_size \
+        "${#XP_ARGS[@]}" \
+        "${XP_OPT_CUSTOM_SIZE_COLS-}" \
+        "${XP_OPT_CUSTOM_SIZE_ROWS-}" \
+        "${XP_WINDOW_HEIGHT}" \
+        "${XP_WINDOW_WIDTH}" \
+        "${XP_OPT_IGNORE_SIZE_LIMIT:-0}")"
+      [[ $? -eq ${XP_ESMLPANE} ]] && exit ${XP_ESMLPANE}
+    fi
+    IFS=" " read -r XP_OPT_CUSTOM_SIZE_COLS XP_OPT_CUSTOM_SIZE_ROWS _tmp_cols <<< "$_tmp_col_row_cols"
+    IFS=" " read -r -a XP_COLS <<< "${_tmp_cols}"
+    IFS=" " read -r -a XP_COLS_OFFSETS <<< "$(printf "%s\\n" "${XP_COLS[*]}" | xpns_nums_accumulate_sum)"
+    xpns_msg_debug "Options: $(xpns_arr2args "${XP_OPTIONS[@]}")"
+    xpns_msg_debug "Arguments: $(xpns_arr2args "${XP_ARGS[@]}")"
+  }
+
+  _pane_base_index=$(xpns_get_global_tmux_conf 'pane-base-index')
+  _last_args_idx=$((${#XP_ARGS[@]} - 1))
+  _def_allow_rename="$(xpns_get_global_tmux_conf 'allow-rename')"
+
+  xpns_suppress_allow_rename "${_def_allow_rename-}"
+  XP_CMD_UTILITY="$(xpns_get_joined_begin_commands "${XP_CMD_UTILITY}")"
+
+  if [[ ${XP_OPT_EXTRA} -eq 1 ]];then
+    # Reuse existing window name
+    # tmux 1.6 does not support -F option
+    _window_name="$( ${TMUX_XPANES_EXEC} display -p -F "#{window_id}" )"
+    _pane_count="$( ${TMUX_XPANES_EXEC} list-panes | grep -c . )"
+    _pane_base_index=$(( _pane_base_index + _pane_count - 1 ))
+    _pane_active_pane_id=$(${TMUX_XPANES_EXEC} display -p -F "#{pane_id}")
+  else
+    _window_name=$(
+      xpns_generate_window_name \
+        "${XP_EMPTY_STR}" \
+        "${XP_ARGS[0]}" \
+        | xpns_value2key)
+  fi
+
+  ## --------------------
+  # Prepare window and panes
+  ## --------------------
+  if [[ ${XP_OPT_EXTRA} -eq 1 ]];then
+    xpns_prepare_extra_panes \
+      "${_window_name}" \
+      "${_pane_base_index}" \
+      "${XP_OPT_LOG_STORE}" \
+      "${XP_OPT_SET_TITLE}" \
+      "${XP_OPT_SPEEDY}" \
+      "${XP_OPT_SPEEDY_AWAIT}" \
+      "${XP_REPSTR}" \
+      "${XP_CMD_UTILITY}" \
+      "${XP_ARGS[@]}"
+  elif [[ ${XP_OPT_USE_PRESET_LAYOUT} -eq 1 ]];then
+    xpns_prepare_preset_layout_window \
+      "${_window_name}" \
+      "${_pane_base_index}" \
+      "${XP_OPT_LOG_STORE}" \
+      "${XP_OPT_SET_TITLE}" \
+      "${XP_OPT_ATTACH}" \
+      "${XP_OPT_SPEEDY}" \
+      "${XP_OPT_SPEEDY_AWAIT}" \
+      "${XP_REPSTR}" \
+      "${XP_CMD_UTILITY}" \
+      "${XP_ARGS[@]}"
+  elif [[ ${XP_OPT_USE_PRESET_LAYOUT} -eq 0 ]];then
+    xpns_prepare_window \
+      "${_window_name}" \
+      "${XP_OPT_LOG_STORE}" \
+      "${XP_OPT_SET_TITLE}" \
+      "${XP_OPT_ATTACH}" \
+      "${XP_OPT_SPEEDY}" \
+      "${XP_OPT_SPEEDY_AWAIT}" \
+      "${XP_REPSTR}" \
+      "${XP_CMD_UTILITY}" \
+      "${XP_ARGS[@]}"
+  fi
+
+  ## With -ss option, it is possible to close all the panes as of here.
+  ## Check status of the window. If no window exists, there is nothing to do execpt to exit.
+  xpns_msg_debug "xpns_is_window_alive:1: After window separation"
+  xpns_is_window_alive "${_window_name}" "${XP_OPT_SPEEDY_AWAIT}" "${_def_allow_rename-}"
+
+  if [[ ${XP_OPT_EXTRA} -eq 1 ]];then
+    # Set offset to avoid sending command to the original pane.
+    _pane_base_index=$((_pane_base_index + 1))
+    # Avoid to make layout even-horizontal even if there are many panes.
+    # in xpns_organize_panes
+    _last_args_idx=$((_last_args_idx + _pane_count))
+    # Re-select the windown that was active before.
+    ${TMUX_XPANES_EXEC} select-pane -t "${_window_name}.${_pane_active_pane_id}"
+  fi
+
+  if [[ ${XP_OPT_LOG_STORE} -eq 1 ]]; then
+    xpns_enable_logging \
+      "${_window_name}" \
+      "${_pane_base_index}" \
+      "${TMUX_XPANES_LOG_DIRECTORY}" \
+      "${TMUX_XPANES_LOG_FORMAT}" \
+      "${XP_EMPTY_STR}" \
+      "${XP_ARGS[@]}"
+
+    if [[ $XP_OPT_SPEEDY -eq 1 ]]; then
+      xpns_notify_logging \
+        "${_window_name}" \
+        "${XP_ARGS[@]}"
+    fi
+  fi
+
+  xpns_msg_debug "xpns_is_window_alive:2: After logging"
+  xpns_is_window_alive "${_window_name}" "${XP_OPT_SPEEDY_AWAIT}" "${_def_allow_rename-}"
+
+  # Set pane titles for each pane.
+  if xpns_is_pane_title_required "${XP_OPT_SET_TITLE}" "${XP_OPT_EXTRA}" ;then
+    xpns_set_titles \
+      "${_window_name}" \
+      "${_pane_base_index}" \
+      "${XP_ARGS[@]}"
+  fi
+
+  if [[ $XP_OPT_SPEEDY -eq 1 ]];then
+    xpns_notify_sync \
+      "${_window_name}" \
+      "${XP_ARGS[@]}"
+  fi
+
+  xpns_msg_debug "xpns_is_window_alive:3: After setting title"
+  xpns_is_window_alive "${_window_name}" "${XP_OPT_SPEEDY_AWAIT}" "${_def_allow_rename-}"
+
+  # Sending operations for each pane.
+  # With -s option, command is already sent.
+  if [[ $XP_OPT_SPEEDY -eq 0 ]]; then
+    xpns_send_commands \
+      "${_window_name}" \
+      "${_pane_base_index}" \
+      "${XP_REPSTR}" \
+      "${XP_CMD_UTILITY}" \
+      "${XP_ARGS[@]}"
+  fi
+
+  xpns_msg_debug "xpns_is_window_alive:4: After sending commands"
+  xpns_is_window_alive "${_window_name}" "${XP_OPT_SPEEDY_AWAIT}" "${_def_allow_rename-}"
+
+  ## With -l <layout>, panes are organized.
+  ## As well as -x, they are re-organized.
+  if [[ $XP_OPT_USE_PRESET_LAYOUT -eq 1 ]] || [[ ${XP_OPT_EXTRA} -eq 1 ]]; then
+    xpns_organize_panes \
+      "${_window_name}" \
+      "${_last_args_idx}"
+  fi
+
+  # Enable broadcasting
+  if [[ ${XP_OPT_IS_SYNC} -eq 1 ]] && [[ ${XP_OPT_EXTRA} -eq 0 ]]; then
+    ${TMUX_XPANES_EXEC} \
+      set-window-option -t "${_window_name}" \
+      synchronize-panes on
+  fi
+
+  ## In case of -t option
+  if [[ ${XP_OPT_SET_TITLE} -eq 1 ]] && [[ ${XP_OPT_CHANGE_BORDER} -eq 1 ]]; then
+    # Set border format
+    ${TMUX_XPANES_EXEC} \
+      set-window-option -t "${_window_name}" \
+      pane-border-format "${TMUX_XPANES_PANE_BORDER_FORMAT}"
+    # Show border status
+    ${TMUX_XPANES_EXEC} \
+      set-window-option -t "${_window_name}" \
+      pane-border-status "${TMUX_XPANES_PANE_BORDER_STATUS}"
+  fi
+
+  # In case of -x, this statement is skipped to keep the original window name
+  if [[ ${XP_OPT_EXTRA} -eq 0 ]];then
+    # Restore original window name.
+    ${TMUX_XPANES_EXEC} \
+      rename-window -t "${_window_name}" \
+      -- "$(printf "%s\\n" "${_window_name}" | xpns_key2value)"
+  fi
+
+  xpns_restore_allow_rename "${_def_allow_rename-}"
+}
+
+## ----------------
+# Arrange options for pipe mode
+#  * argument -> command
+#  * stdin -> argument
+## ----------------
+xpns_switch_pipe_mode() {
+  local _pane_num4new_term=""
+  if [[ -n "${XP_ARGS[*]-}" ]] && [[ -n "${XP_CMD_UTILITY-}" ]]; then
+    xpns_msg_error "Both arguments and other options (like '-c', '-e') which updates <command> are given."
+    exit ${XP_EINVAL}
+  fi
+
+  if [[ -z "${TMUX-}" ]]; then
+    xpns_msg_warning "Attached session is required for 'Pipe mode'."
+    # This condition is used when the following situations.
+    #   * Enter from outside of tmux session(Normal mode1)
+    #   * Pipe mode.
+    #   * -n option.
+    #
+    # For example:
+    #     (Normal mode1)$ echo {a..g} | ./xpanes -n 2
+    # => This will once create the new window like this.
+    #     (inside of tmux session)$ ./xpanes '-n' '2' 'a' 'b' 'c' 'd' 'e' 'f' 'g'
+    #     => After the window is closed, following panes would be left.
+    #     (pane 1)$ echo a b
+    #     (pane 2)$ echo c d
+    #     (pane 3)$ echo e f
+    #     (pane 4)$ echo g
+    # In order to create such the query,
+    # separate all the argument into minimum tokens
+    # with xargs -n 1
+    if [[ -n "${XP_MAX_PANE_ARGS-}" ]]; then
+      _pane_num4new_term=1
+    fi
+  fi
+
+  while read -r line;
+  do
+    XP_STDIN+=("${line}")
+  done < <(cat | xpns_rm_empty_line | \
+    xpns_pipe_filter "${_pane_num4new_term:-${XP_MAX_PANE_ARGS}}")
+
+
+  # Merge them into command.
+  if [[ -n "${XP_ARGS[*]-}" ]]; then
+    # Attention: It might be wrong result if IFS is changed.
+    XP_CMD_UTILITY="${XP_ARGS[*]}"
+  fi
+
+  # If there is empty -I option or user does not assign the <repstr>,
+  # Append the space and <repstr> at the end of the <command>
+  # This is same as the xargs command of GNU.
+  # i.e,
+  #   $ echo 10 | xargs seq
+  #     => seq 10
+  # Whith is same as
+  #   $ echo 10 | xargs -I@ seq @
+  #     => seq 10
+  if [[ -z "${XP_REPSTR}" ]]; then
+    XP_REPSTR="${XP_DEFAULT_REPSTR}"
+    if [[ -n "${XP_ARGS[*]-}" ]]; then
+      XP_CMD_UTILITY="${XP_ARGS[*]-} ${XP_REPSTR}"
+    fi
+  fi
+
+  # Deal with stdin as arguments.
+  XP_ARGS=("${XP_STDIN[@]-}")
+}
+
+xpns_layout_short2long() {
+  sed \
+    -e 's/^t$/tiled/' \
+    -e 's/^eh$/even-horizontal/' \
+    -e 's/^ev$/even-vertical/' \
+    -e 's/^mh$/main-horizontal/' \
+    -e 's/^mv$/main-vertical/' \
+    -e ';'
+}
+
+xpns_is_valid_layout() {
+  local _layout="${1-}"
+  local _pat='^(tiled|even-horizontal|even-vertical|main-horizontal|main-vertical)$'
+  if ! [[ $_layout =~ $_pat ]]  ; then
+    xpns_msg_error "Invalid layout '${_layout}'."
+    exit ${XP_ELAYOUT}
+  fi
+}
+
+xpns_warning_before_extra() {
+  local _ans=
+  local _synchronized=
+  _synchronized="$(xpns_get_local_tmux_conf "synchronize-panes")"
+  if [[ "on" == "${_synchronized}" ]];then
+    xpns_msg_warning "Panes are now synchronized.
+'-x' option may cause unexpected behavior on the synchronized panes."
+    printf "Are you really sure? [y/n]: "
+    read -r _ans
+    if ! [[ "${_ans-}" =~ ^[yY]  ]]; then
+      return 1
+    fi
+  fi
+}
+
+xpns_load_flag_options() {
+  if [[ "$1" =~ h ]]; then
+    xpns_usage
+    exit 0
+  fi
+  if [[ "$1" =~ V ]]; then
+    xpns_version
+    exit 0
+  fi
+  if [[ "$1" =~ x ]]; then
+    XP_OPT_EXTRA=1
+    XP_OPT_USE_PRESET_LAYOUT=1 ## Layout presets must be used with -x
+    if ! xpns_warning_before_extra; then
+      exit ${XP_EINTENT}
+    fi
+  fi
+  if [[ "$1" =~ d ]]; then
+    XP_OPT_IS_SYNC=0
+  fi
+  if [[ "$1" =~ e ]]; then
+    XP_REPSTR="{}"
+    XP_CMD_UTILITY="{}"
+  fi
+  if [[ "$1" =~ t ]]; then
+    if ( xpns_tmux_is_greater_equals 2.3 ) ; then
+      XP_OPT_SET_TITLE=1
+      XP_OPT_CHANGE_BORDER=1
+    else
+      xpns_msg_warning "-t option cannot be used by tmux version less than 2.3. Disabled."
+      sleep 1
+    fi
+  fi
+  if [[ "$1" =~ s ]]; then
+    XP_OPT_SPEEDY=1
+    XP_OPT_SPEEDY_AWAIT=1
+  fi
+  if [[ "$1" =~ ss ]]; then
+    XP_OPT_SPEEDY_AWAIT=0
+  fi
+  return 1
+}
+
+xpns_load_arg_options() {
+  # Extract flag options only.
+  local _pattern=
+  xpns_load_flag_options "$(xpns_extract_matched "$1" "^-${XP_FLAG_OPTIONS}+")" > /dev/null
+  if [[ "$1" =~ ^-${XP_FLAG_OPTIONS}*I ]]; then
+    # Behavior like this.
+    # -IAAA       -- XP_REPSTR="AAA"
+    # -I AAA BBB  -- XP_REPSTR="AAA", XP_ARGS=("BBB")
+    # -I"AAA BBB" -- XP_REPSTR="AAA BBB"
+    # -IAAA BBB   -- XP_REPSTR="AAA", XP_ARGS=("BBB")
+    # -I -d ...   -- XP_REPSTR=""
+    _pattern="^-${XP_FLAG_OPTIONS}*I(.+)"
+    if [[ "$1" =~ $_pattern ]]; then
+      XP_REPSTR="${BASH_REMATCH[1]}"
+      return 0
+    elif ! [[ "$2" =~ ^-.* ]]; then
+      XP_REPSTR="$2"
+      return 0
+    else
+      xpns_msg_error "invalid argument '$2' for -I option"
+      exit ${XP_EINVAL}
+    fi
+  elif [[ "$1" =~ ^-${XP_FLAG_OPTIONS}*l ]]; then
+    _pattern="^-${XP_FLAG_OPTIONS}*l(.+)"
+    if [[ "$1" =~ $_pattern ]]; then
+      XP_OPT_USE_PRESET_LAYOUT=1
+      XP_LAYOUT="$(cat <<<"${BASH_REMATCH[1]}" | xpns_layout_short2long)"
+      xpns_is_valid_layout "${XP_LAYOUT}"
+      return 0
+    elif ! [[ "$2" =~ ^-.* ]]; then
+      XP_OPT_USE_PRESET_LAYOUT=1
+      XP_LAYOUT="$(cat <<<"$2" |  xpns_layout_short2long )"
+      xpns_is_valid_layout "${XP_LAYOUT}"
+      return 0
+    else
+      xpns_msg_error "invalid argument '$2' for -l option"
+      exit ${XP_EINVAL}
+    fi
+  elif [[ "$1" =~ ^-${XP_FLAG_OPTIONS}*c ]]; then
+    _pattern="^-${XP_FLAG_OPTIONS}*c(.+)"
+    if [[ "$1" =~ $_pattern ]]; then
+      XP_CMD_UTILITY="${BASH_REMATCH[1]}"
+      XP_OPT_CMD_UTILITY=1
+      return 0
+    elif ! [[ "$2" =~ ^-.* ]]; then
+      XP_CMD_UTILITY="$2"
+      XP_OPT_CMD_UTILITY=1
+      return 0
+    else
+      xpns_msg_error "invalid argument '$2' for -c option"
+      exit ${XP_EINVAL}
+    fi
+  elif [[ "$1" =~ ^-${XP_FLAG_OPTIONS}*n ]]; then
+    _pattern="^-${XP_FLAG_OPTIONS}*n([0-9]+)"
+    if [[ "$1" =~ $_pattern ]]; then
+      XP_MAX_PANE_ARGS="${BASH_REMATCH[1]}"
+      return 0
+    elif [[ "$2" =~ ^[0-9]+$ ]]; then
+      XP_MAX_PANE_ARGS="$2"
+      return 0
+    else
+      xpns_msg_error "invalid argument '$2' for -n option"
+      exit ${XP_EINVAL}
+    fi
+  elif [[ "$1" =~ ^-${XP_FLAG_OPTIONS}*S ]]; then
+    _pattern="^-${XP_FLAG_OPTIONS}*S(.+)"
+    if [[ "$1" =~ $_pattern ]]; then
+      XP_SOCKET_PATH="${BASH_REMATCH[1]}"
+      return 0
+    elif ! [[ "$2" =~ ^-.* ]]; then
+      XP_SOCKET_PATH="$2"
+      return 0
+    else
+      xpns_msg_error "invalid argument '$2' for -S option"
+      exit ${XP_EINVAL}
+    fi
+  elif [[ "$1" =~ ^-${XP_FLAG_OPTIONS}*C ]]; then
+    _pattern="^-${XP_FLAG_OPTIONS}*C([0-9]+)"
+    if [[ "$1" =~ $_pattern ]]; then
+      XP_OPT_CUSTOM_SIZE_COLS="${BASH_REMATCH[1]}"
+      return 0
+    elif [[ "$2" =~ ^[0-9]+$ ]];then
+      XP_OPT_CUSTOM_SIZE_COLS="$2"
+      return 0
+    else
+      xpns_msg_error "invalid argument '$2' for -C option"
+      exit ${XP_EINVAL}
+    fi
+  elif [[ "$1" =~ ^-${XP_FLAG_OPTIONS}*R ]]; then
+    _pattern="^-${XP_FLAG_OPTIONS}*R([0-9]+)"
+    if [[ "$1" =~ $_pattern ]]; then
+      XP_OPT_CUSTOM_SIZE_ROWS="${BASH_REMATCH[1]}"
+      return 0
+    elif [[ "$2" =~ ^[0-9]+$ ]];then
+      XP_OPT_CUSTOM_SIZE_ROWS="$2"
+      return 0
+    else
+      xpns_msg_error "invalid argument '$2' for -R option"
+      exit ${XP_EINVAL}
+    fi
+  elif [[ "$1" =~ ^-${XP_FLAG_OPTIONS}*B ]]; then
+    _pattern="^-${XP_FLAG_OPTIONS}*B(.+)"
+    if [[ "$1" =~ $_pattern ]]; then
+      XP_BEGIN_ARGS+=("${BASH_REMATCH[1]}")
+      return 0
+    else
+      XP_BEGIN_ARGS+=("$2")
+      return 0
+    fi
+  fi
+  return 0
+}
+
+xpns_load_long_options() {
+  if [[ "$1" =~ ^--help$ ]]; then
+    xpns_usage
+    exit 0
+  elif [[ "$1" =~ ^--version$ ]]; then
+    xpns_version
+    exit 0
+  elif [[ "$1" =~ ^--desync$ ]]; then
+    XP_OPT_IS_SYNC=0
+    return 1
+  elif [[ "$1" =~ ^--log-format=.*$ ]]; then
+    XP_OPT_LOG_STORE=1
+    TMUX_XPANES_LOG_FORMAT="${1#--log-format=}"
+    return 1
+  elif [[ "$1" =~ ^--log ]]; then
+    XP_OPT_LOG_STORE=1
+    if [[ "$1" =~ ^--log=.*$  ]]; then
+      TMUX_XPANES_LOG_DIRECTORY="${1#--log=}"
+    fi
+    return 1
+  elif [[ "$1" =~ ^--ssh$ ]]; then
+    XP_CMD_UTILITY="${XP_SSH_CMD_UTILITY}"
+    # Enable -t option as well
+    XP_OPT_SET_TITLE=1
+    XP_OPT_CHANGE_BORDER=1
+    # Enable -s option
+    XP_OPT_SPEEDY=1
+    XP_OPT_SPEEDY_AWAIT=1
+    return 1
+  elif [[ "$1" =~ ^--stay$ ]]; then
+    XP_OPT_ATTACH=0
+    return 1
+  elif [[ "$1" =~ ^--cols=[0-9]+$ ]]; then
+    XP_OPT_CUSTOM_SIZE_COLS="${1#--cols=}"
+    return 1
+  elif [[ "$1" =~ ^--rows=[0-9]+$ ]]; then
+    XP_OPT_CUSTOM_SIZE_ROWS="${1#--rows=}"
+    return 1
+  elif [[ "$1" =~ ^--bulk-cols=[0-9,]*[0-9]+$ ]]; then
+    XP_OPT_BULK_COLS="${1#--bulk-cols=}"
+    return 1
+  elif [[ "$1" =~ ^--debug$ ]]; then
+    XP_OPT_DEBUG=1
+    return 1
+  elif [[ "$1" =~ ^--dry-run$ ]]; then # For unit testing
+    XP_OPT_DRY_RUN=1
+    return 1
+  elif [[ "$1" =~ ^--ignore-size-limit$ ]]; then
+    XP_OPT_IGNORE_SIZE_LIMIT=1
+    return 1
+
+  ## ----------------
+  # Other options
+  ## ----------------
+  else
+    xpns_msg_error "invalid option -- '${1#--}'"
+    xpns_usage_warn
+    exit ${XP_EINVAL}
+  fi
+}
+
+xpns_parse_options() {
+  while (( $# > 0 )); do
+    case "$1" in
+      --)
+      if [[ ${XP_NO_OPT} -eq 1 ]]; then
+        XP_ARGS+=("$1")
+        shift
+      else
+        # Disable any more options
+        XP_NO_OPT=1
+        shift
+      fi
+      ;;
+      ## ----------------
+      # Long options
+      ## ----------------
+      --*)
+      if [[ ${XP_NO_OPT} -eq 1 ]]; then
+        XP_ARGS+=("$1")
+        shift
+      else
+        local _shift_count="0"
+        xpns_load_long_options "$@"
+        _shift_count="$?"
+        [[ "${_shift_count}" = "1" ]] && XP_OPTIONS+=("$1") && shift
+      fi
+      ;;
+      ## ----------------
+      # Short options
+      ## ----------------
+      -*)
+      if [[ ${XP_NO_OPT} -eq 1 ]]; then
+        XP_ARGS+=("$1")
+        shift
+      else
+        local _shift_count="0"
+        if [[ "$1" =~ ^-${XP_FLAG_OPTIONS}*${XP_ARG_OPTIONS}. ]];then
+          xpns_load_arg_options "$@"
+          XP_OPTIONS+=("$1") && shift
+        elif [[ "$1" =~ ^-${XP_FLAG_OPTIONS}*${XP_ARG_OPTIONS}$ ]] && [[ -n "${2-}" ]];then
+          xpns_load_arg_options "$@"
+          _shift_count="$?"
+          XP_OPTIONS+=("$1" "$2") && shift && shift
+        elif [[ "$1" =~ ^-${XP_FLAG_OPTIONS}+$  ]];then
+          xpns_load_flag_options "$1"
+          XP_OPTIONS+=("$1") && shift
+        ## ----------------
+        # Other options
+        ## ----------------
+        else
+          xpns_msg_error "Invalid option -- '${1#-}'"
+          xpns_usage_warn
+          exit ${XP_EINVAL}
+        fi
+      fi
+      ;;
+      ## ----------------
+      # Other arguments
+      ## ----------------
+      *)
+      XP_ARGS+=("$1")
+      XP_NO_OPT=1
+      shift
+      ;;
+    esac
+  done
+
+  # If there is any standard input from pipe,
+  # 1 line handled as 1 argument.
+  if [[ ! -t 0 ]]; then
+    XP_IS_PIPE_MODE=1
+    xpns_switch_pipe_mode
+  fi
+
+  # When no argument arr given, exit.
+  if [[ -z "${XP_ARGS[*]-}" ]]; then
+    xpns_msg_error "No arguments."
+    xpns_usage_warn
+    exit ${XP_EINVAL}
+  fi
+
+  if [[ -n "${XP_OPT_CUSTOM_SIZE_COLS-}" ]] || [[ -n "${XP_OPT_CUSTOM_SIZE_ROWS-}" ]]; then
+    if [[ "$XP_OPT_EXTRA" -eq 1 ]]; then
+      xpns_msg_warning "The columns/rows options (-C, --cols, -R, --rows) cannot be used with -x option. Ignored."
+    elif [[ "$XP_OPT_EXTRA" -eq 0 ]] && [[ "${XP_OPT_USE_PRESET_LAYOUT}" -eq 1 ]]; then
+      # This part is required to keep backward compatibility.
+      ## Users can simulate xpanes v3.x to set : alias xpanes="xpanes -lt"
+      xpns_msg_info "Columns/rows option (-C, --cols, -R, --rows) and -l option are provided. Disable -l. "
+      XP_OPT_USE_PRESET_LAYOUT=0
+    fi
+  fi
+
+  # Set default value in case of empty.
+  XP_CMD_UTILITY="${XP_CMD_UTILITY:-${XP_DEFAULT_CMD_UTILITY}}"
+  XP_REPSTR="${XP_REPSTR:-${XP_DEFAULT_REPSTR}}"
+
+  # To set command on pre_execution, set -c option manually.
+  if [[ ${XP_OPT_CMD_UTILITY} -eq 0 ]];then
+    XP_OPTIONS+=("-c" "${XP_CMD_UTILITY}")
+  fi
+
+}
+
+## --------------------------------
+# Main function
+## --------------------------------
+xpns_main() {
+  xpns_parse_options ${1+"$@"}
+  xpns_check_env "${XP_DEPENDENCIES}"
+  ## --------------------------------
+  # Parameter validation
+  ## --------------------------------
+  # When do dry-run flag is enabled, skip running (this is used to execute unit test of itself).
+  if [[ ${XP_OPT_DRY_RUN} -eq 1 ]]; then
+    return 0
+  fi
+  # Validate log directory.
+  if [[ ${XP_OPT_LOG_STORE} -eq 1 ]]; then
+    TMUX_XPANES_LOG_DIRECTORY=$(xpns_normalize_directory "${TMUX_XPANES_LOG_DIRECTORY}")
+    xpns_is_valid_directory "${TMUX_XPANES_LOG_DIRECTORY}" && \
+    TMUX_XPANES_LOG_DIRECTORY=$(cd "${TMUX_XPANES_LOG_DIRECTORY}" && pwd)
+  fi
+  ## --------------------------------
+  # If current shell is outside of tmux session.
+  ## --------------------------------
+  if [[ -z "${TMUX-}" ]]; then
+    xpns_pre_execution
+  ## --------------------------------
+  # If current shell is already inside of tmux session.
+  ## --------------------------------
+  else
+    xpns_execution
+  fi
+  exit 0
+}
+
+## --------------------------------
+# Entry Point
+## --------------------------------
+xpns_main ${1+"$@"}
index c398bca54a26feb1985ed6403d8a670509edff49..d34ad592b37246a9d7789a6b92d53d3df5d231f4 100644 (file)
@@ -1,5 +1,6 @@
 *
 !/.bin/mtmux
 !/.bin/tmux
+!/.bin/xpanes
 !/.gitignore.d/tmux
 !/.tmux.conf