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