├── .gitignore ├── docs ├── images │ ├── index.png │ ├── row-example-1.png │ ├── row-example-2.png │ ├── column-example-1.png │ ├── column-example-2.png │ └── column-example-3.png ├── tmux.md ├── development.md └── architecture.md ├── .yamllint ├── focus.tmux ├── docker-compose.yml ├── .github ├── release.yml ├── dependabot.yml └── workflows │ ├── release.yml │ └── tests.yml ├── .shellspec ├── .editorconfig ├── CHANGELOG.md ├── spec ├── spec_helper.sh └── functions_spec.sh ├── .pre-commit-config.yaml ├── LICENSE ├── Dockerfile ├── .tmux.conf ├── scripts ├── menu.sh ├── functions.sh └── focus.sh └── README.md /.gitignore: -------------------------------------------------------------------------------- 1 | coverage 2 | -------------------------------------------------------------------------------- /docs/images/index.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/graemedavidson/tmux-pane-focus/HEAD/docs/images/index.png -------------------------------------------------------------------------------- /docs/images/row-example-1.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/graemedavidson/tmux-pane-focus/HEAD/docs/images/row-example-1.png -------------------------------------------------------------------------------- /docs/images/row-example-2.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/graemedavidson/tmux-pane-focus/HEAD/docs/images/row-example-2.png -------------------------------------------------------------------------------- /docs/images/column-example-1.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/graemedavidson/tmux-pane-focus/HEAD/docs/images/column-example-1.png -------------------------------------------------------------------------------- /docs/images/column-example-2.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/graemedavidson/tmux-pane-focus/HEAD/docs/images/column-example-2.png -------------------------------------------------------------------------------- /docs/images/column-example-3.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/graemedavidson/tmux-pane-focus/HEAD/docs/images/column-example-3.png -------------------------------------------------------------------------------- /.yamllint: -------------------------------------------------------------------------------- 1 | --- 2 | yaml-files: 3 | - '*.yaml' 4 | - '*.yml' 5 | - .yamllint 6 | 7 | extends: default 8 | 9 | rules: 10 | indentation: 11 | level: warning 12 | indent-sequences: consistent 13 | line-length: disable 14 | truthy: disable 15 | -------------------------------------------------------------------------------- /docs/tmux.md: -------------------------------------------------------------------------------- 1 | # Tmux 2 | 3 | ## Hooks 4 | 5 | Pane Focus utilises a tmux hook to activate the script. List available hooks for session, window, and pane. The list 6 | includes hooks currently configured. 7 | 8 | ```bash 9 | tmux show-hooks -s # show all hooks available for the session. 10 | ``` 11 | -------------------------------------------------------------------------------- /focus.tmux: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env bash 2 | 3 | CURRENT_DIR="$( cd "$( dirname "${BASH_SOURCE[0]}" )" && pwd )" 4 | 5 | tmux set-hook -g after-select-pane "run-shell '$CURRENT_DIR/scripts/focus.sh'" 6 | tmux set-hook -g after-split-window "run-shell '$CURRENT_DIR/scripts/focus.sh'" 7 | 8 | tmux bind-key T run-shell "$CURRENT_DIR/scripts/menu.sh" 9 | -------------------------------------------------------------------------------- /docker-compose.yml: -------------------------------------------------------------------------------- 1 | --- 2 | version: '3.9' 3 | 4 | name: tmux-pane-focus-plugin 5 | 6 | services: 7 | tmux: 8 | build: 9 | context: . 10 | dockerfile: Dockerfile 11 | volumes: 12 | - .tmux.conf:/home/developer/.tmux.conf 13 | - .:/home/developer/.tmux/plugins/tmux-pane-focus 14 | 15 | tests: 16 | image: shellspec/shellspec-debian:0.28.1 17 | volumes: 18 | - .:/src 19 | command: ["-s", "bash"] 20 | -------------------------------------------------------------------------------- /.github/release.yml: -------------------------------------------------------------------------------- 1 | --- 2 | changelog: 3 | exclude: 4 | labels: 5 | - ignore-for-release 6 | categories: 7 | - title: Breaking Changes 8 | labels: 9 | - Semver-Major 10 | - breaking-change 11 | - title: New Features 12 | labels: 13 | - Semver-Minor 14 | - enhancement 15 | - title: Other Changes 16 | labels: 17 | - "*" 18 | - title: Dependencies (dependabot) 19 | labels: 20 | - dependencies 21 | -------------------------------------------------------------------------------- /.github/dependabot.yml: -------------------------------------------------------------------------------- 1 | --- 2 | # https://docs.github.com/github/administering-a-repository/configuration-options-for-dependency-updates 3 | 4 | version: 2 5 | updates: 6 | - package-ecosystem: github-actions 7 | directory: / 8 | schedule: 9 | interval: monthly 10 | commit-message: 11 | prefix: "ci" 12 | - package-ecosystem: docker 13 | directory: / 14 | schedule: 15 | interval: monthly 16 | commit-message: 17 | prefix: "ci" 18 | -------------------------------------------------------------------------------- /.shellspec: -------------------------------------------------------------------------------- 1 | --require spec_helper 2 | 3 | ## Default kcov (coverage) options 4 | --kcov-options "--include-path=./scripts/functions.sh --path-strip-level=1" 5 | --kcov-options "--exclude-pattern=/.shellspec,/spec/,/coverage/,/report/" 6 | 7 | ## Example: Include script "myprog" with no extension 8 | # --kcov-options "--include-pattern=.sh,myprog" 9 | 10 | ## Example: Only specified files/directories 11 | # --kcov-options "--include-pattern=myprog,/lib/" 12 | -------------------------------------------------------------------------------- /.editorconfig: -------------------------------------------------------------------------------- 1 | # EditorConfig is awesome: http://EditorConfig.org 2 | # Uses editorconfig to maintain consistent coding styles 3 | 4 | # top-most EditorConfig file 5 | root = true 6 | 7 | [*] 8 | indent_style = space 9 | indent_size = 2 10 | charset = utf-8 11 | trim_trailing_whitespace = true 12 | insert_final_newline = true 13 | end_of_line = lf 14 | 15 | [Dockerfile] 16 | indent_size = 4 17 | 18 | [*.md] 19 | trim_trailing_whitespace = false 20 | indent_size = 4 21 | max_line_length = 120 22 | -------------------------------------------------------------------------------- /CHANGELOG.md: -------------------------------------------------------------------------------- 1 | # CHANGELOG 2 | https://keepachangelog.com/en/1.0.0/ 3 | 4 | ## [v0.4.0-alpha] - 05/03/24 5 | 6 | ### Added 7 | 8 | - hook to resize when creating new panes within a window. 9 | 10 | ### Fixed 11 | 12 | - debug log option using incorrect value when set in menu. 13 | 14 | ## [v0.3.0-alpha] - 25/02/24 15 | 16 | ### Added 17 | 18 | - Basic releases workflow to generate GitHub release when pushing a tag. 19 | - Define release config for use with issue labelling 20 | - Changelog 21 | -------------------------------------------------------------------------------- /.github/workflows/release.yml: -------------------------------------------------------------------------------- 1 | --- 2 | name: Release 3 | 4 | on: 5 | push: 6 | tags: 7 | - 'v*' 8 | 9 | jobs: 10 | release: 11 | runs-on: ubuntu-latest 12 | steps: 13 | - name: Checkout repo 14 | uses: actions/checkout@v4 15 | 16 | - name: Generate release notes 17 | run: | 18 | gh release create ${{github.ref_name}} --generate-notes --latest --notes "[Changelog](./CHANGELOG.md), [Documentation](./docs)" 19 | env: 20 | GH_TOKEN: ${{ github.token }} 21 | -------------------------------------------------------------------------------- /spec/spec_helper.sh: -------------------------------------------------------------------------------- 1 | # shellcheck shell=sh 2 | 3 | # Defining variables and functions here will affect all specfiles. 4 | # Change shell options inside a function may cause different behavior, 5 | # so it is better to set them here. 6 | # set -eu 7 | 8 | # This callback function will be invoked only once before loading specfiles. 9 | spec_helper_precheck() { 10 | # Available functions: info, warn, error, abort, setenv, unsetenv 11 | # Available variables: VERSION, SHELL_TYPE, SHELL_VERSION 12 | : minimum_version "0.28.1" 13 | } 14 | 15 | # This callback function will be invoked after a specfile has been loaded. 16 | spec_helper_loaded() { 17 | : 18 | } 19 | 20 | # This callback function will be invoked after core modules has been loaded. 21 | spec_helper_configure() { 22 | # Available functions: import, before_each, after_each, before_all, after_all 23 | : import 'support/custom_matcher' 24 | } 25 | -------------------------------------------------------------------------------- /.pre-commit-config.yaml: -------------------------------------------------------------------------------- 1 | --- 2 | repos: 3 | - repo: https://github.com/pre-commit/pre-commit-hooks 4 | rev: v4.5.0 5 | hooks: 6 | - id: check-json 7 | - id: end-of-file-fixer 8 | - id: trailing-whitespace 9 | 10 | - repo: https://github.com/adrienverge/yamllint.git 11 | rev: v1.35.0 12 | hooks: 13 | - id: yamllint 14 | args: [--format, parsable, --strict] 15 | 16 | - repo: https://github.com/jumanjihouse/pre-commit-hook-yamlfmt 17 | rev: 0.2.3 18 | hooks: 19 | - id: yamlfmt 20 | args: [--mapping, '2', --sequence, '2', --offset, '0', --preserve-quotes] 21 | 22 | - repo: https://github.com/hadolint/hadolint 23 | rev: v2.12.0 24 | hooks: 25 | - id: hadolint-docker 26 | name: Lint Dockerfiles 27 | description: Runs hadolint Docker image to lint Dockerfiles 28 | language: docker_image 29 | types: ["dockerfile"] 30 | entry: ghcr.io/hadolint/hadolint hadolint 31 | 32 | - repo: https://github.com/koalaman/shellcheck-precommit 33 | rev: v0.9.0 34 | hooks: 35 | - id: shellcheck 36 | 37 | - repo: https://github.com/rhysd/actionlint 38 | rev: v1.6.26 39 | hooks: 40 | - id: actionlint 41 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2023 Graeme Davidson 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /Dockerfile: -------------------------------------------------------------------------------- 1 | FROM ubuntu:jammy-20250404 2 | 3 | # https://packages.ubuntu.com/ 4 | RUN apt-get update && apt-get install -y --no-install-recommends \ 5 | build-essential=12.9ubuntu3 \ 6 | ca-certificates=20230311ubuntu0.22.04.1 \ 7 | fzf=0.29.0-1 \ 8 | git=1:2.34.1-1ubuntu1.9 \ 9 | libevent-dev=2.1.12-stable-1build3 \ 10 | libncurses-dev=6.3-2ubuntu0.1 \ 11 | wget=1.21.2-2ubuntu1 \ 12 | bison=2:3.8.2+dfsg-1build1 \ 13 | byacc=1:2.0.20220114-1 \ 14 | && apt-get clean \ 15 | && rm -rf /var/lib/apt/lists/* 16 | 17 | RUN update-ca-certificates 18 | 19 | # https://github.com/tmux/tmux/releases 20 | ARG TMUX_VERSION=3.4 21 | RUN mkdir /opt/tmux 22 | WORKDIR /opt/tmux 23 | RUN wget --progress=dot:giga https://github.com/tmux/tmux/releases/download/${TMUX_VERSION}/tmux-${TMUX_VERSION}.tar.gz && \ 24 | tar xzf tmux-${TMUX_VERSION}.tar.gz 25 | WORKDIR /opt/tmux/tmux-${TMUX_VERSION} 26 | 27 | RUN ./configure && \ 28 | make && \ 29 | make install && \ 30 | ln -s /opt/tmux/tmux-${TMUX_VERSION}/tmux /usr/bin/tmux 31 | RUN useradd -ms /bin/bash developer && \ 32 | usermod -aG sudo developer 33 | USER developer 34 | WORKDIR /home/developer 35 | 36 | COPY .tmux.conf /home/developer/ 37 | RUN git clone https://github.com/tmux-plugins/tpm ~/.tmux/plugins/tpm && ~/.tmux/plugins/tpm/bin/install_plugins 38 | 39 | ENTRYPOINT ["tmux"] 40 | -------------------------------------------------------------------------------- /.tmux.conf: -------------------------------------------------------------------------------- 1 | # Example Configuration File 2 | 3 | unbind C-b 4 | set -g prefix C-a 5 | bind C-a send-prefix 6 | 7 | set -g mouse on 8 | 9 | bind R source-file /home/developer/.tmux.conf \; display-message "Config reloaded..." 10 | 11 | # reassign split window keybinds to something more memorable 12 | unbind % 13 | bind | split-window -h -c "#{pane_current_path}" 14 | bind - split-window -v -c "#{pane_current_path}" 15 | bind c new-window -c "#{pane_current_path}" 16 | 17 | # Use Alt-arrow keys without prefix key to switch panes 18 | bind -n M-Left select-pane -L 19 | bind -n M-Right select-pane -R 20 | bind -n M-Up select-pane -U 21 | bind -n M-Down select-pane -D 22 | 23 | # List of plugins: https://github.com/tmux-plugins/list 24 | set -g @plugin 'tmux-plugins/tpm' 25 | set -g @plugin 'tmux-plugins/tmux-sensible' 26 | 27 | # Install through github disabled as not useful for local development 28 | # set -g @plugin 'graemedavidson/tmux-focus' 29 | 30 | set -g @pane-focus-enabled on 31 | set -g @pane-focus-size '50' 32 | set -g @pane-focus-direction '+' 33 | 34 | set -g status-right '#[fg=colour255,bg=colour237][#{@pane-focus-direction} #{@pane-focus-size}% #{@pane-focus-enabled}]#[fg=default,bg=default]' 35 | 36 | # Initialize TMUX plugin manager (keep this line at the very bottom of tmux.conf) 37 | run '~/.tmux/plugins/tpm/tpm' 38 | 39 | # Run local plugin added through mounting in docker-compose 40 | run-shell '~/.tmux/plugins/tmux-pane-focus/focus.tmux' 41 | -------------------------------------------------------------------------------- /docs/development.md: -------------------------------------------------------------------------------- 1 | # Local Development 2 | 3 | Local development leverages a basic docker compose and docker file setup. Setup should respond to changes made to the 4 | scripts without requiring restarts. 5 | 6 | - [Docker Build File](../Dockerfile) 7 | - [Docker Compose](../docker-compose.yml) 8 | 9 | Build the local image and run: 10 | 11 | ```bash 12 | docker-compose build 13 | docker-compose run tmux 14 | ``` 15 | 16 | Create and move between new panes: 17 | 18 | Binding actions: 19 | 20 | - `ctrl-a |`: Create vertical pane 21 | - `ctrl-a -`: Create horizontal pane 22 | - `ctrl-a `: Move between panes 23 | - `ctrl-a T`: plugin settings menu 24 | 25 | ## Shellspec Tests 26 | 27 | Unit tests included through [shellspec](https://shellspec.info/) within a [container](https://hub.docker.com/r/shellspec/shellspec-debian/tags). 28 | 29 | - [Tests](../spec/) 30 | 31 | ```bash 32 | docker-compose run tests 33 | docker run -it --rm -v "$PWD:/src" --entrypoint bash shellspec/shellspec-debian:0.28.1 34 | ``` 35 | 36 | ## Tmux Setup 37 | 38 | Tmux configured to use `ctrl-a` as well as other opinionated settings. 39 | 40 | - [tmux config](../.tmux.conf) 41 | 42 | [Tmux Plugin Manager](https://github.com/tmux-plugins/tpm) included with automatic installation of 43 | [Tmux Sensible](https://github.com/tmux-plugins/tmux-sensible). 44 | 45 | ## Pre-Commit 46 | 47 | [Pre-Commit](https://pre-commit.com/). 48 | 49 | - [.pre-commit-config.yaml](../.pre-commit-config.yaml) 50 | 51 | Install pre-commit hooks: 52 | 53 | ```bash 54 | pre-commit install 55 | ``` 56 | 57 | Run against all files: 58 | 59 | ```bash 60 | pre-commit run --all-files 61 | ``` 62 | -------------------------------------------------------------------------------- /scripts/menu.sh: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env bash 2 | # 3 | current_dir="$( cd "$( dirname "${BASH_SOURCE[0]}" )" && pwd )" 4 | # Not working: shellcheck source=./scripts/functions.sh 5 | # shellcheck source=/dev/null 6 | . "${current_dir}/functions.sh" 7 | 8 | read -r enabled< <(get_tmux_option "@pane-focus-enabled" "on") 9 | read -r active_percentage< <(get_tmux_option "@pane-focus-size" "50") 10 | read -r direction< <(get_tmux_option "@pane-focus-direction" "+") 11 | read -r debug< <(get_tmux_option "@pane-focus-debug-log" "false") 12 | 13 | tmux display-menu -T "#[align=centre fg=green]Pane Focus Options" -x R -y P \ 14 | "" \ 15 | "-Pane Size: ${active_percentage}%" "" "" \ 16 | "50%" "5" "set-option -w \"@pane-focus-size\" \"50\"" \ 17 | "60%" "6" "set-option -w \"@pane-focus-size\" \"60\"" \ 18 | "70%" "7" "set-option -w \"@pane-focus-size\" \"70\"" \ 19 | "80%" "8" "set-option -w \"@pane-focus-size\" \"80\"" \ 20 | "90%" "9" "set-option -w \"@pane-focus-size\" \"90\"" \ 21 | "" \ 22 | "-Focus Direction: ${direction}" "" "" \ 23 | "[+] both" "b" "set-option -w \"@pane-focus-direction\" \"+\"" \ 24 | "[|] vertical only" "v" "set-option -w \"@pane-focus-direction\" \"|\"" \ 25 | "[-] horizontal only" "h" "set-option -w \"@pane-focus-direction\" \"-\"" \ 26 | "" \ 27 | "-Enabled: ${enabled}" "" "" \ 28 | "on" "o" "set-option -w \"@pane-focus-enabled\" \"on\"" \ 29 | "off" "f" "set-option -w \"@pane-focus-enabled\" \"off\"" \ 30 | "" \ 31 | "-Debug Log: ${debug}" "" "" \ 32 | "enable" "e" "set-option -w \"@pane-focus-debug-log\" \"true\"" \ 33 | "disable" "d" "set-option -w \"@pane-focus-debug-log\" \"false\"" \ 34 | "" \ 35 | "Close menu" "q" "" 36 | -------------------------------------------------------------------------------- /.github/workflows/tests.yml: -------------------------------------------------------------------------------- 1 | --- 2 | name: Linting and Unit Tests 3 | 4 | on: 5 | push: 6 | branches: ['**'] 7 | paths-ignore: 8 | - .github/dependabot.yml 9 | - .github/workflows/release.yml 10 | - '*.md' 11 | - '*.yaml' 12 | - .gitignore 13 | - LICENSE 14 | pull_request: 15 | branches: [main] 16 | paths-ignore: 17 | - .github/dependabot.yml 18 | - .github/workflows/release.yml 19 | - '*.md' 20 | - '*.yaml' 21 | - .gitignore 22 | - LICENSE 23 | 24 | jobs: 25 | linting: 26 | runs-on: ubuntu-latest 27 | steps: 28 | - name: Checkout repo 29 | uses: actions/checkout@v4 30 | 31 | - name: hadolint 32 | uses: hadolint/hadolint-action@v3.1.0 33 | with: 34 | dockerfile: ./Dockerfile 35 | id: hadolint 36 | 37 | - name: ShellCheck 38 | uses: ludeeus/action-shellcheck@2.0.0 39 | id: shellcheck 40 | 41 | unittests: 42 | needs: linting 43 | runs-on: ubuntu-latest 44 | steps: 45 | - name: Checkout repo 46 | uses: actions/checkout@v4 47 | 48 | - name: Install kcov 49 | run: | 50 | sudo apt-get update 51 | sudo apt-get install -y bash git kcov 52 | id: install-dependencies 53 | 54 | - name: Install Shellspec 55 | run: | 56 | cd /opt/ 57 | sudo git clone https://github.com/shellspec/shellspec.git 58 | sudo ln -s /opt/shellspec/shellspec /usr/local/bin/shellspec 59 | id: install-shellspec 60 | 61 | - name: Run shellspec with kcov 62 | env: 63 | SHELLSPEC_KCOV_OPTS: "--include-pattern=*.sh" 64 | run: | 65 | shellspec -s bash --kcov 66 | 67 | - uses: codecov/codecov-action@v5 68 | if: github.ref == 'refs/heads/main' 69 | with: 70 | token: ${{ secrets.CODECOV_TOKEN }} 71 | files: ./coverage/cobertura.xml 72 | flags: unittests 73 | fail_ci_if_error: true 74 | verbose: true 75 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # tmux pane focus 2 | 3 | [![codecov](https://codecov.io/gh/graemedavidson/tmux-pane-focus/branch/main/graph/badge.svg?token=2ULOAGT6BT)](https://codecov.io/gh/graemedavidson/tmux-pane-focus) 4 | 5 | Tmux plugin to auto resize panes on focus similar to [nvim Focus](https://github.com/beauwilliams/focus.nvim). 6 | 7 | Utilises [tmux hooks](./docs/tmux.md#hooks) to react to the creation and selection of panes. 8 | 9 | - Size: >=50, <100. 10 | - Direction: 11 | - `+`: both 12 | - `|`: width changes only 13 | - `-`: height changes only 14 | 15 | Plugin still in an alpha stage. Currently testing locally to confirm features and find bugs. A refactor is coming as 16 | currently not in a DRY state with a lack of naming clarity and commenting hindering contributing. Feel free to use 17 | though and open an issue or start a discussion. 18 | 19 | ## Installation 20 | 21 | ### Tmux Plugin Manager 22 | 23 | Add plugin GitHub url to list of tpm plugins. Specify tag/branch for specific version. 24 | 25 | ``` 26 | set -g @plugin 'graemedavidson/tmux-pane-focus' 27 | # set -g @plugin 'graemedavidson/tmux-pane-focus#tag' 28 | ``` 29 | 30 | ### Manual 31 | 32 | Clone repo into tmux plugins dir. 33 | 34 | Add run shell command to end of `.tmux.conf` file to activate plugin. 35 | 36 | ```conf 37 | run-shell '~/.tmux/plugins/tmux-pane-focus/focus.tmux' 38 | ``` 39 | 40 | ## Configuration 41 | 42 | Changes to configuration require a tmux config reload or new tmux session to pickup changes. Adding the following 43 | configuration can simply the process for testing which also requires a restart to start using. 44 | 45 | ```bash 46 | bind R source-file ~/.tmux.conf \; display-message "Config reloaded..." 47 | ``` 48 | 49 | Enable/Disable plugin: 50 | 51 | ``` 52 | set -g @pane-focus-enabled on 53 | ``` 54 | 55 | Add configuration to the `.tmux.conf` file to override the following defaults: 56 | 57 | - focus size: `50%` 58 | - focus direction: `+` 59 | 60 | ```conf 61 | set -g @pane-focus-size '50' 62 | set -g @pane-focus-direction '+' 63 | ``` 64 | 65 | ### Settings Menu 66 | 67 | The default and global settings can be overridden at the window level through an options menu. 68 | 69 | tmux shortcut: `ctrl-a T`. 70 | 71 | ### Status bar 72 | 73 | Add current active size and direction to status bar: 74 | 75 | ```conf 76 | set -g status-right '#[fg=colour255,bg=colour237][#{@pane-focus-direction}][#{@pane-focus-size}]#[fg=default,bg=default]' 77 | ``` 78 | -------------------------------------------------------------------------------- /spec/functions_spec.sh: -------------------------------------------------------------------------------- 1 | #!/bin/shellspec shell=bash 2 | 3 | Describe 'check get_active_pane' 4 | Include scripts/functions.sh 5 | 6 | # window height 7 | # window width 8 | # active percentage 9 | # mock data (index height width top bottom left right) 10 | # result (index resize_height resize_width min_height min_width top bottom left right height width) 11 | Parameters 12 | 100 100 50 "0-50-100-0-49-0-99" "0-false-false-49-49-0-49-0-99-50-100" 13 | 100 100 60 "0-50-100-0-49-0-99" "0-true-false-59-59-0-49-0-99-50-100" 14 | 100 100 70 "0-50-100-0-49-0-99" "0-true-false-69-69-0-49-0-99-50-100" 15 | 100 100 80 "0-50-100-0-49-0-99" "0-true-false-79-79-0-49-0-99-50-100" 16 | 100 100 90 "0-50-100-0-49-0-99" "0-true-false-89-89-0-49-0-99-50-100" 17 | 18 | 100 100 50 "0-25-25-0-49-0-99" "0-true-true-49-49-0-49-0-99-25-25" 19 | 100 100 60 "0-25-25-0-49-0-99" "0-true-true-59-59-0-49-0-99-25-25" 20 | 100 100 70 "0-25-25-0-49-0-99" "0-true-true-69-69-0-49-0-99-25-25" 21 | 100 100 80 "0-25-25-0-49-0-99" "0-true-true-79-79-0-49-0-99-25-25" 22 | 100 100 90 "0-25-25-0-49-0-99" "0-true-true-89-89-0-49-0-99-25-25" 23 | 24 | 1000 1000 50 "0-250-250-0-249-0-249" "0-true-true-499-499-0-249-0-249-250-250" 25 | End 26 | 27 | It 'returns correct values for active pane' 28 | 29 | shellspec_mock tmux <<-EOF 30 | echo "${4}" 31 | EOF 32 | 33 | When call get_active_pane "${1}" "${2}" "${3}" 34 | The output should eq "${5}" 35 | End 36 | End 37 | 38 | Describe 'check in_col_row' 39 | Include scripts/functions.sh 40 | 41 | # side a 42 | # side a active 43 | # side b 44 | # side b active 45 | # result (bool) 46 | Parameters 47 | 0 0 100 100 true 48 | 0 50 100 100 true 49 | 25 0 100 100 true 50 | 0 25 125 100 true 51 | 52 | 0 50 49 100 false 53 | 51 0 100 50 false 54 | 0 75 25 100 false 55 | 75 0 100 50 false 56 | End 57 | 58 | It 'returns true if inactive pane within boundaries of active pane, other false' 59 | When call in_col_row "${1}" "${2}" "${3}" "${4}" 60 | The output should eq "${5}" 61 | End 62 | End 63 | 64 | Describe 'check get_inactive_pane_size' 65 | Include scripts/functions.sh 66 | 67 | # window size 68 | # active percentage 69 | # number of panes 70 | # result (minimum inactive pane size) 71 | Parameters 72 | 100 50 1 50 73 | 100 60 1 40 74 | 100 70 1 30 75 | 100 80 1 20 76 | 100 90 1 10 77 | 78 | 100 50 2 25 79 | 100 60 2 20 80 | 100 70 2 15 81 | 100 80 2 10 82 | 100 90 2 5 83 | 84 | # Bash uses integer maths, so rounds down to whole numbers 85 | 100 50 3 16 86 | 100 60 3 13 87 | 100 70 3 10 88 | 100 80 3 6 89 | 100 90 3 3 90 | End 91 | 92 | It 'return the minimum size of an inactive pane' 93 | When call get_inactive_pane_size "${1}" "${2}" "${3}" 94 | The output should eq "${4}" 95 | End 96 | End 97 | 98 | Describe 'check get_tmux_option' 99 | Include scripts/functions.sh 100 | 101 | # option 102 | # default value 103 | # mock result (-w) 104 | # mock result (-g) 105 | # result (option value) 106 | Parameters 107 | "@pane-focus-enabled" "on" "on" "" "on" 108 | "@pane-focus-enabled" "on" "" "on" "on" 109 | "@pane-focus-enabled" "on" "" "" "on" 110 | "@pane-focus-enabled" "on" "off" "" "off" 111 | "@pane-focus-enabled" "on" "" "off" "off" 112 | 113 | "@pane-focus-size" "50" "50" "" "50" 114 | "@pane-focus-size" "50" "" "50" "50" 115 | "@pane-focus-size" "50" "" "" "50" 116 | "@pane-focus-size" "50" "60" "" "60" 117 | "@pane-focus-size" "50" "" "60" "60" 118 | 119 | "@pane-focus-direction" "+" "+" "" "+" 120 | "@pane-focus-direction" "+" "" "+" "+" 121 | "@pane-focus-direction" "+" "" "" "+" 122 | "@pane-focus-direction" "+" "|" "" "|" 123 | "@pane-focus-direction" "+" "" "|" "|" 124 | "@pane-focus-direction" "+" "-" "" "-" 125 | "@pane-focus-direction" "+" "" "-" "-" 126 | End 127 | 128 | set_tmux_option() { 129 | echo "" > /dev/null 130 | } 131 | 132 | tmux() { 133 | local sub_command="${1}" 134 | local flags="${2}" 135 | 136 | if [[ "${sub_command}" == "show-options" ]]; then 137 | if [[ "${flags}" == "-wqv" ]]; then 138 | echo "${window_val}" 139 | elif [[ "${flags}" == "-gqv" ]]; then 140 | echo "${global_val}" 141 | fi 142 | fi 143 | } 144 | 145 | It 'returns set value or default' 146 | export -a window_val="${3}" 147 | export -a global_val="${4}" 148 | 149 | When call get_tmux_option "${1}" "${2}" 150 | The output should eq "${5}" 151 | End 152 | End 153 | 154 | Describe 'check get_inactive_parent_pane_count' 155 | Include scripts/functions.sh 156 | 157 | Parameters 158 | 0 0 159 | 0 0 0 0 160 | 0 0 0 0 0 0 0 0 0 0 0 161 | 1 0 0 0 1 0 0 0 0 0 0 162 | 2 0 0 0 1 1 0 0 0 0 0 163 | 4 0 0 0 1 1 0 0 0 1 1 164 | End 165 | 166 | Describe "Functionality with parameter (result): $1 and array (test data): ${*:2}" 167 | It 'returns count of inactive parent panes' 168 | expected_result="$1" 169 | array_values=("${@:2}") 170 | 171 | When call get_inactive_parent_pane_count "${array_values[@]}" 172 | The output should eq "${expected_result}" 173 | End 174 | End 175 | End 176 | -------------------------------------------------------------------------------- /scripts/functions.sh: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env bash 2 | 3 | # Check if the active pane requires resize 4 | # 5 | # Parameter(s): 6 | # - window_height (integer): height of window 7 | # - window_width (integer): width of window 8 | # - active_percentage (integer): size in percentage required for active pane 9 | # 10 | # Return(s): 11 | # - index (interger): index of active pane 12 | # - resize_height (string): true|false value indicating if resize required 13 | # - resize_width (string): true|false value indicating if resize required 14 | # - min_height (interger): minimum size of active pane height 15 | # - min_width (interger): minimum size of active pane width 16 | # - top (interger): pane top value 17 | # - bottom (interger): pane bottom value 18 | # - left (interger): pane left value 19 | # - right (interger): pane right value 20 | get_active_pane() { 21 | local window_height="${1}" 22 | local window_width="${2}" 23 | local active_percentage="${3}" 24 | 25 | IFS=- read -r index height width top bottom left right< <(tmux list-panes -F "#{pane_index}-#{pane_height}-#{pane_width}-#{pane_top}-#{pane_bottom}-#{pane_left}-#{pane_right}" -f "#{m:1,#{pane_active}}") 26 | 27 | local min_height=$(((window_height * active_percentage / 100) - 1)) 28 | local min_width=$(((window_width * active_percentage / 100) - 1)) 29 | 30 | local resize_height=false 31 | local resize_width=false 32 | if [[ "${height}" -lt "${min_height}" ]]; then 33 | local resize_height=true 34 | fi 35 | if [[ "${width}" -lt "${min_width}" ]]; then 36 | local resize_width=true 37 | fi 38 | 39 | echo -n "${index}-${resize_height}-${resize_width}-${min_height}-${min_width}-${top}-${bottom}-${left}-${right}-${height}-${width}" 40 | } 41 | 42 | # Determine inactive pane size 43 | # 44 | # Parameter(s): 45 | # - window_size (integer): height/width of window 46 | # - active_percentage (integer): size in percentage required for active pane 47 | # - number of inactive panes (integer): the number of inactive panes in row/col of active pane 48 | # 49 | # Return(s): 50 | # - resize_size (string): min size of inactive pane 51 | get_inactive_pane_size() { 52 | local window_size="${1}" 53 | local active_percentage="${2}" 54 | local num_panes="${3}" 55 | 56 | local inactive_percentage=$((100 - active_percentage)) 57 | local min_inactive=$(((window_size * inactive_percentage / 100) / num_panes)) 58 | echo "${min_inactive}" 59 | } 60 | 61 | # Resize a tmux pane by percentage 62 | # 63 | # Parameter(s): 64 | # - pane_index (interger): unique id for tmux pane to be changed 65 | # - pane_height (integer): height value to change pane to 66 | # - pane_width (integer): width value to change pane to 67 | resize_pane() { 68 | pane_index="${1}" 69 | pane_height="${2}" 70 | pane_width="${3}" 71 | 72 | if [[ ${pane_height} -gt 0 ]] && [[ ${pane_width} -gt 0 ]]; then 73 | tmux resize-pane -t "${pane_index}" -y "${pane_height}" -x "${pane_width}" 74 | elif [[ $pane_height -gt 0 ]]; then 75 | tmux resize-pane -t "${pane_index}" -y "${pane_height}" 76 | elif [[ $pane_width -gt 0 ]]; then 77 | tmux resize-pane -t "${pane_index}" -x "${pane_width}" 78 | fi 79 | } 80 | 81 | # Get a tmux option value 82 | # 83 | # checks local setting and if unset checks and then sets global (-g) for inital default setting set 84 | # in `.tmux.conf`. 85 | # 86 | # Parameter(s): 87 | # - option (string): name of tmux option to get 88 | # 89 | # Return(s): 90 | # - option_val (string): value of tmux setting 91 | get_tmux_option() { 92 | local option="${1}" 93 | local default_value="${2}" 94 | 95 | read -r option_value< <(tmux show-options -wqv "${option}") 96 | if [[ -z "${option_value}" ]]; then 97 | # Try global (-g) 98 | read -r option_value< <(tmux show-options -gqv "${option}") 99 | fi 100 | if [[ -z "${option_value}" ]]; then 101 | local option_value="${default_value}" 102 | fi 103 | set_tmux_option "${option}" "${option_value}" 104 | 105 | echo "${option_value}" 106 | } 107 | 108 | # Set a tmux option value locally 109 | # 110 | # Parameter(s): 111 | # - option (string): name of tmux option to set 112 | # - option_value (string): value of tmux option to set 113 | set_tmux_option() { 114 | local option="${1}" 115 | local option_value="${2}" 116 | tmux set-option -w "${option}" "${option_value}" 117 | } 118 | 119 | # Determine if inactive pane inside boundaries of active pane col or row 120 | # 121 | # Takes left and right values for determining column or top and bottom for row. 122 | # 123 | # Parameter(s): 124 | # - side_a (integer): value of side of inactive pane 125 | # - side_a_active (integer): value of side of active pane for comparison 126 | # - side_b (integer): value of side of inactive pane 127 | # - side_b_active (integer): value of side of active pane for comparison 128 | # 129 | # Return(s): 130 | # - result (bool): true if inactive pane within boundaries of active pane, else false 131 | in_col_row() { 132 | local side_a="${1}" 133 | local side_a_active="${2}" 134 | local side_b="${3}" 135 | local side_b_active="${4}" 136 | 137 | local result=false 138 | 139 | if [[ "${side_a}" -ge "${side_a_active}" ]] && [[ "${side_a}" -le "${side_b_active}" ]]; then 140 | local result=true 141 | elif [[ "${side_b}" -le "${side_b_active}" ]] && [[ "${side_b}" -ge "${side_a_active}" ]]; then 142 | local result=true 143 | elif [[ "${side_a}" -le "${side_a_active}" ]] && [[ "${side_b}" -ge "${side_b_active}" ]]; then 144 | local result=true 145 | fi 146 | 147 | echo "${result}" 148 | } 149 | 150 | # Get count of inactive parent panes (panes with children) 151 | # 152 | # Returns a count of inactive parent panes (panes with children) that are not in the col/row of focused pane that also have child panes. 153 | # 154 | # Parameter(s): 155 | # - panes (list): list of panes 156 | # 157 | # Return(s): 158 | # - count (interger): Number of inactive parent panes. 159 | get_inactive_parent_pane_count() { 160 | local panes=("$@") 161 | 162 | local count=0 163 | for pane_index in "${!panes[@]}"; do 164 | if [[ "${panes[${pane_index}]}" -gt 0 ]]; then 165 | ((count=count+1)) 166 | fi 167 | done 168 | 169 | echo "${count}" 170 | } 171 | 172 | # Write to temporary debug log file 173 | # 174 | # Parameter(s): 175 | # - messages (array): debug lines 176 | # 177 | write_debug_line() { 178 | local messages=("$@") 179 | 180 | for message in "${messages[@]}"; do 181 | echo -e "${message}" >> /tmp/tmux-pane-focus.log 182 | done 183 | } 184 | -------------------------------------------------------------------------------- /docs/architecture.md: -------------------------------------------------------------------------------- 1 | # Architecture 2 | 3 | Plugin uses window height and width and the current active percentage size setting to calculate the minimum size of 4 | active pane. If the active pane is lower than the calculated size a list of panes within the row or column of the active 5 | pane calculated and then updated. If the active pane is greater than the calculated minimum no changes occur. 6 | 7 | The plugin makes use of the tmux API to modify tmux state. Review tmux man page or [online documentation](http://man.openbsd.org/OpenBSD-current/man1/tmux.1) 8 | 9 | ## Glossary 10 | 11 | - row: horizontal set of panes in which the active pane sits. 12 | - col/column: vertical set of panes in which the active pane sits. 13 | - height: integer value for window or pane. 14 | - width: integer value for window or pane. 15 | - horizontal splits `_`: the number of splits separating panes top to bottom. 16 | - vertical splits `|`: the number of splits separating panes left to right. 17 | - window: A window within a tmux session containing panes 18 | - pane: a individual command prompt within a window 19 | - pane sides: integer value for position within grid (top, bottom, left, and right) 20 | - axis: y representing height and, x representing width 21 | 22 | Plugin refers to the x plane as `row` and the y plane as `column` within the code. 23 | 24 | ## Change Ordering 25 | 26 | Currently configured to change size of height then width. 27 | 28 | ## Pane Limits 29 | 30 | Plugin determines the number of panes at start. 31 | 32 | if 1 pane, quit. 33 | 34 | Currently no upper limit on panes defined, considering changes as resolving complicated layouts with several panes leads 35 | to more erroneous results. 36 | 37 | ## Active Pane 38 | 39 | The plugins main function is to resize the active pane selected by the user to the currently configured active 40 | percentage size. The plugin calculates the remaining screen space divided by number of inactive panes and resizes 41 | accordingly. Design intended to leave inactive panes legible where possible. 42 | 43 | ## Rows and Columns 44 | 45 | Plugin optimised to determine all panes sitting on the x and y planes of the active pane. Therefore all inactive panes 46 | outside of the active pane not resized to reduce plugin operations. Plugin accounts for overlapping panes. 47 | 48 | The following examples show each inactive pane found in the column and row of the active pane. 49 | 50 | Row Examples: 51 | 52 | ![Row Example 1](./images/row-example-1.png) 53 | ![Row Example 2](./images/row-example-2.png) 54 | 55 | Column Examples: 56 | 57 | ![Column Example 1](./images/column-example-1.png) 58 | ![Column Example 2](./images/column-example-2.png) 59 | ![Column Example 2](./images/column-example-3.png) 60 | 61 | ## Parent Panes 62 | 63 | When tmux resizes a pane any adjacent panes on the column or row is also resized, so for example, a resize command on 64 | pane 1 would change the size of panes 2, 3 and 4 in the below diagrams. The last diagram should return to the same as 65 | the start diagram. 66 | 67 | ``` 68 | # start select pane 1 (increase) select pane 0 (decrease) 69 | --------------- --------------- --------------- 70 | | 0 | 1 | | 0 | 1 | | 0 | 1 | 71 | | |-----| | |----------| | |-----| 72 | | | 2|3 | | | 2 | 3 | | | 2|3 | 73 | | |-----| | |----------| | |-----| 74 | | | 4 | | | 4 | | | 4 | 75 | --------------- --------------- --------------- 76 | ``` 77 | 78 | Plugin determines parent panes by maintaining a stack and pushing when greater value of left or top and pushing when 79 | lower value of left or top. This matches the internal tmux index ordering which moves left to write then top down, so in 80 | the below example pane 3 sits under pane 2, not to the right of it. 81 | 82 | ``` 83 | --------------- 84 | | 0 | 1 | 85 | | |-------| 86 | | | 2 | 4 | 87 | | |-------| 88 | | | 3 | 5 | 89 | --------------- 90 | ``` 91 | 92 | To account for this parent panes have a multiplier value calculated for each child pane and children with same 93 | dimensions aren't added to the resize list. Below examples describe the expectations in greater detail. 94 | 95 | Parents determined for rows and columns separately. 96 | 97 | ### Column example 98 | 99 | Changing width when selecting pane 1 100 | 101 | ``` 102 | --------------- 103 | | 0 | 1 | 104 | | |-------| 105 | | | 2 | 3 | 106 | | |-------| 107 | | | 4 | 108 | --------------- 109 | ``` 110 | 111 | - Pane 2, 3, and 4 are children of pane 1. 112 | - Pane 1, 2, and 3 added to list of panes requiring resize. 113 | - Pane 4 omitted from resize list; same size of parent pane on left and right margins. 114 | - Pane 1 has multiplier of 2. 115 | - Pane 0 resized to active size 116 | - Pane 1 resized to inactive size * 2 117 | - Pane 2, and 3 resized to inactive size 118 | 119 | ### Row example 120 | 121 | Changing height when selecting pane 1 122 | 123 | ``` 124 | --------------- 125 | | 0 | 126 | |---------------| 127 | | 1 | 2 | 3 | 128 | |---------------| 129 | | 4 | 5 | 130 | --------------- 131 | ``` 132 | 133 | - Pane 0, 4, and 5 omitted as not in active pane row. 134 | - Pane 1 not a parent pane as its the active pane. 135 | - Pane 3 child of pane 2. 136 | - Pane 3 omitted from resize list; same size of parent pane on top and bottom margins 137 | - Pane 1 resized to active size 138 | - Pane 2 resized to inactive size 139 | 140 | ## Resizing 141 | 142 | Plugin makes use of the `tmux resize-pane` command to change a pane. 143 | 144 | Targeting of a pane by `-t` parameter when running the resize command. Command accepts an id with the `%` prefix or 145 | an index number. The plugin uses the index value internally for uniquely identifying each pane. 146 | 147 | The ordering of changes is from top left to bottom right following the inbuilt [indexing](#indexes) of tmux. 148 | 149 | ### Indexes 150 | 151 | The indexes of current panes shown with command: 152 | 153 | ``` 154 | Ctrl-A q 155 | ``` 156 | 157 | List all panes indexes in order: 158 | 159 | ``` 160 | tmux list-panes -F "#{pane_index}" | sort -n 161 | ``` 162 | 163 | ![Pane indexes](./images/index.png) 164 | 165 | ## Language 166 | 167 | Plugin language is bash matching the majority of tmux plugins. Bash allows for portability but the language has 168 | limitations. Considerations to moving towards another scripting language for example python in pipeline. 169 | 170 | ## Global and Local variables 171 | 172 | Global variable accessed using the `-g` flag and within the context of this plugin provides access to the configuration 173 | set in the [tmux conf file](#configuration) used as default for all new sessions and windows. Global settings can be 174 | overridden per window with the [settings menu](#setting-menu). The menu uses the `-w` flag to set the option at the 175 | window level. 176 | -------------------------------------------------------------------------------- /scripts/focus.sh: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env bash 2 | 3 | current_dir="$( cd "$( dirname "${BASH_SOURCE[0]}" )" && pwd )" 4 | # Not working: shellcheck source=./scripts/functions.sh 5 | # shellcheck source=/dev/null 6 | . "${current_dir}/functions.sh" 7 | 8 | pane_count=$(tmux list-panes | wc -l) 9 | if [[ $pane_count -eq 1 ]]; then 10 | exit 0 11 | fi 12 | 13 | read -r enabled< <(get_tmux_option "@pane-focus-enabled" "on") 14 | if [[ "${enabled}" == "off" ]]; then 15 | exit 16 | fi 17 | 18 | read -r active_percentage< <(get_tmux_option "@pane-focus-size" "50") 19 | if [[ "${active_percentage}" -lt 50 ]] || [[ "${active_percentage}" -ge 100 ]]; then 20 | tmux display-message "#[bg=red]Invalid @pane-focus-size setting in .tmux.conf file: ${active_percentage}; expected value between 50 and 100.#[bg=default]" 21 | exit 22 | fi 23 | 24 | # Check session and then global (default in .tmux.conf), set session 25 | read -r direction< <(get_tmux_option "@pane-focus-direction" "+") 26 | if [[ ! "${direction}" =~ (\+|\-|\|) ]]; then 27 | tmux display-message "#[bg=red]Invalid @pane-focus-direction setting in .tmux.conf file: ${direction}; expected value '+', '|', '-'.#[bg=default]" 28 | exit 29 | fi 30 | 31 | read -r debug< <(get_tmux_option "@pane-focus-debug-log" "false") 32 | if [[ ! "${debug}" =~ (true|false) ]]; then 33 | tmux display-message "#[bg=red]Invalid @pane-focus-debug-log setting: ${debug}; expected value 'true', 'false'.#[bg=default]" 34 | exit 35 | fi 36 | 37 | resize_height_setting=true 38 | resize_width_setting=true 39 | if [[ "${direction}" == "|" ]]; then 40 | resize_height_setting=false 41 | fi 42 | if [[ "${direction}" == "-" ]]; then 43 | resize_width_setting=false 44 | fi 45 | 46 | IFS=- read -r window_height window_width < <(tmux list-windows -F "#{window_height}-#{window_width}" -f "#{m:1,#{window_active}}") 47 | IFS=- read -r active_pane_index resize_height resize_width active_min_height active_min_width active_top active_bottom active_left active_right _ _< <(get_active_pane "${window_height}" "${window_width}" "${active_percentage}") 48 | 49 | panes=$(tmux list-panes -F "#{pane_index}-#{pane_left}-#{pane_top}-#{pane_right}-#{pane_bottom}-#{pane_active}" | sort -n) 50 | 51 | if [[ "${debug}" = "true" ]] ; then 52 | tmux display-message "#[bg=yellow]Debug logging enabled (/tmp/tmux-pane-focus.log).#[bg=default]" 53 | write_debug_line "-------------------------------------" \ 54 | "active percentage: ${active_percentage}" \ 55 | "resize height: ${resize_height_setting}, resize width: ${resize_width_setting}, direction: ${direction}" \ 56 | "window height: ${window_height}, width: ${window_width}" \ 57 | "panes: [${panes[*]}]" 58 | fi 59 | 60 | if [[ "${resize_height}" == "true" ]] && [[ "${resize_height_setting}" == "true" ]]; then 61 | resize_height_panes=() 62 | declare -A inactive_height_parent_panes 63 | parents=() 64 | prev_top=0 65 | prev_bottom=0 66 | prev_left=0 67 | prev_index=0 68 | 69 | for pane in ${panes}; do 70 | IFS=- read -r index left top right bottom _< <(echo "${pane}") 71 | inactive_height_parent_panes+=( ["${index}"]=0 ) 72 | 73 | read -r in_column< <(in_col_row "${left}" "${active_left}" "${right}" "${active_right}") 74 | if [[ "${in_column}" == "false" ]]; then 75 | continue 76 | fi 77 | resize_height_panes+=("${index}") 78 | 79 | if [[ "${left}" -eq "${prev_top}" ]] && [[ "${bottom}" -eq "${prev_bottom}" ]]; then 80 | unset "resize_height_panes[-1]" 81 | elif [[ "${top}" -ge "${prev_top}" ]] && [[ "${bottom}" -le "${prev_bottom}" ]]; then 82 | if [[ "${left}" -gt "${prev_left}" ]]; then 83 | parents+=("${prev_index}") 84 | elif [[ "${left}" -lt "${prev_left}" ]]; then 85 | unset 'parents[-1]' 86 | fi 87 | # Update pane count for previous pane index 88 | ((inactive_height_parent_panes["${parents[-1]}"]=inactive_height_parent_panes["${parents[-1]}"]+1)) 89 | else 90 | prev_top="${top}" 91 | prev_bottom="${bottom}" 92 | fi 93 | prev_top="${top}" 94 | prev_index="${index}" 95 | done 96 | fi 97 | 98 | if [[ "${resize_width}" == "true" ]] && [[ "${resize_width_setting}" == "true" ]]; then 99 | resize_width_panes=() 100 | declare -A inactive_width_parent_panes 101 | parents=() 102 | prev_left=0 103 | prev_right=0 104 | prev_top=0 105 | prev_index=0 106 | 107 | for pane in ${panes}; do 108 | IFS=- read -r index left top right bottom _< <(echo "${pane}") 109 | inactive_width_parent_panes+=( ["${index}"]=0 ) 110 | read -r in_row< <(in_col_row "${top}" "${active_top}" "${bottom}" "${active_bottom}") 111 | if [[ "${in_row}" == "false" ]]; then 112 | continue 113 | fi 114 | resize_width_panes+=("${index}") 115 | 116 | if [[ "${left}" -eq "${prev_left}" ]] && [[ "${right}" -eq "${prev_right}" ]]; then 117 | unset "resize_width_panes[-1]" 118 | elif [[ "${left}" -ge "${prev_left}" ]] && [[ "${right}" -le "${prev_right}" ]]; then 119 | if [[ "${top}" -gt "${prev_top}" ]]; then 120 | parents+=("${prev_index}") 121 | elif [[ "${top}" -lt "${prev_top}" ]]; then 122 | unset 'parents[-1]' 123 | fi 124 | # Update pane count for previous pane index 125 | ((inactive_width_parent_panes["${parents[-1]}"]=inactive_width_parent_panes["${parents[-1]}"]+1)) 126 | else 127 | prev_left="${left}" 128 | prev_right="${right}" 129 | fi 130 | prev_top="${top}" 131 | prev_index="${index}" 132 | done 133 | fi 134 | 135 | # Count of parent panes 136 | read -r inactive_height_parent_pane_count< <(get_inactive_parent_pane_count "${inactive_height_parent_panes[@]}") 137 | read -r inactive_width_parent_pane_count< <(get_inactive_parent_pane_count "${inactive_width_parent_panes[@]}") 138 | 139 | # Remove active pane from count and parent panes from count 140 | inactive_height_panes="$(( ${#resize_height_panes[@]} - 1))" 141 | if [[ "${inactive_height_panes}" -gt 1 ]]; then 142 | inactive_height_panes="$(( inactive_height_panes - inactive_height_parent_pane_count ))" 143 | fi 144 | inactive_width_panes="$(( ${#resize_width_panes[@]} - 1))" 145 | if [[ "${inactive_width_panes}" -gt 1 ]]; then 146 | inactive_width_panes="$(( inactive_width_panes - inactive_width_parent_pane_count ))" 147 | fi 148 | 149 | IFS=- read -r min_inactive_height< <(get_inactive_pane_size "${window_height}" "${active_percentage}" "${inactive_height_panes}") 150 | IFS=- read -r min_inactive_width< <(get_inactive_pane_size "${window_width}" "${active_percentage}" "${inactive_width_panes}") 151 | 152 | if [[ "${debug}" = "true" ]] ; then 153 | write_debug_line "height:" \ 154 | "\tresize_height_panes: ${resize_height_panes[*]}" \ 155 | "\tinactive_height_parent_panes: ${inactive_height_parent_panes[*]}" \ 156 | "\tresize_height_panes: ${resize_height_panes[*]}" \ 157 | "\tinactive_height_parent_pane_count: ${inactive_height_parent_pane_count}" \ 158 | "\tinactive_height_panes (count): ${inactive_height_panes[*]}" \ 159 | "width:" \ 160 | "\tresize_width_panes: ${resize_width_panes[*]}" \ 161 | "\tinactive_width_parent_panes: ${inactive_width_parent_panes[*]}" \ 162 | "\tresize_width_panes: ${resize_width_panes[*]}" \ 163 | "\tinactive_width_parent_pane_count: ${inactive_width_parent_pane_count}" \ 164 | "\tinactive_width_panes (count): ${inactive_width_panes[*]}" \ 165 | "resize panes:" 166 | fi 167 | 168 | if [[ "${resize_height}" == "true" ]] && [[ "${resize_height_setting}" == "true" ]]; then 169 | for pane_index in "${resize_height_panes[@]}"; do 170 | if [[ "${pane_index}" -eq "${active_pane_index}" ]]; then 171 | resize_value="${active_min_height}" 172 | else 173 | if [[ "${inactive_height_parent_panes[${pane_index}]}" -gt 1 ]]; then 174 | resize_value=$(( min_inactive_height * inactive_height_parent_panes[${pane_index}] )) 175 | else 176 | resize_value="${min_inactive_height}" 177 | fi 178 | fi 179 | resize_pane "${pane_index}" "${resize_value}" 0 180 | if [[ "${debug}" = "true" ]] ; then 181 | write_debug_line "\t- pane: ${pane_index} ${resize_value} 0" 182 | fi 183 | done 184 | fi 185 | 186 | if [[ "${resize_width}" == "true" ]] && [[ "${resize_width_setting}" == "true" ]]; then 187 | for pane_index in "${resize_width_panes[@]}"; do 188 | if [[ "${pane_index}" -eq "${active_pane_index}" ]]; then 189 | resize_value="${active_min_width}" 190 | else 191 | if [[ "${inactive_width_parent_panes[${pane_index}]}" -gt 1 ]]; then 192 | resize_value=$(( min_inactive_width * inactive_width_parent_panes["${pane_index}"] )) 193 | else 194 | resize_value="${min_inactive_width}" 195 | fi 196 | fi 197 | resize_pane "${pane_index}" 0 "${resize_value}" 198 | if [[ "${debug}" = "true" ]] ; then 199 | write_debug_line "\t- pane: ${pane_index} 0 ${resize_value}" 200 | fi 201 | done 202 | fi 203 | --------------------------------------------------------------------------------