6

I'd like to create a "focus mode" for the terminal using tmux, sort of like what goyo does for vim:

goyo screenshot

Problem 1: sizing the panes

My thinking was to achieve this by splitting the window into three panes, then setting the center pane to a fixed max value (maybe 80 columns? Maybe 100? Maybe different maxes depending on terminal window size, like responsive web layouts?) and setting the side panes to fill the remaining columns equally. I'd also like this to work on screens of varying sizes, so that I can use it on both my laptop and my desktop.

Currently, the only way I can think to do this is with cooperation from the shell (i.e., to get the terminal width with tput cols and then calculate the width needed for each pane with math operations using $(())). I'm okay with this, but if there were a built-in way to accomplish this in tmux, I'd feel really silly for missing it.

So... is there?

Problem 2: blanking the side panes

This is less crucial; it's really not so bad to see a mostly blank pane with a prompt at the top. I know that tmux has clock-mode (for instance), which overrides the contents of the panes and displays something else instead. So I'm wondering if there's a way to hide the content of an inactive pane entirely. So far, the best I can come up with is to set the fg and bg to the same color:

:select-pane -t:.1 -P 'fg=black,bg=black'

Or, the Holy Grail

This could all be accomplished much more easily if there were a facility for margins on a pane in tmux. To my knowledge, this feature does not exist, but I'm far from a tmux expert. Is there any chance it's there and I just don't know about it?

5

This is what I have under the name _tmux-margins:

#!/bin/sh

pane="${2:-$TMUX_PANE}"
[ -n "$pane" ] || exit 1
tmux has-session -t "$pane" 2>/dev/null || exit 0

tool="$0"
width="${TMUX_M_COLUMNS:-80}"
step=10
todo="${TMUX_M_COMMAND:-true}"
action="$1"
pattern=" '$tool' sleep "
pattern_left="$pattern'${pane}' left"
pattern_right="$pattern'${pane}' right"
command_left="TMUX_M_COMMAND='$todo'$pattern_left"
command_right="TMUX_M_COMMAND='$todo'$pattern_right"

tmux display-message -p '#{pane_start_command}' | grep -qF "$pattern" && exit 0

find_margins() {
    pane_left="$(tmux list-panes -F "#{pane_id} #{pane_start_command}" | grep -F "$pattern_left" | head -n 1 | cut -d ' ' -f 1)"
    pane_right="$(tmux list-panes -F "#{pane_id} #{pane_start_command}" | grep -F "$pattern_right" | head -n 1 | cut -d ' ' -f 1)"
}

find_geometry() {
    [ -n "$pane_left" ] && width_left="$(tmux display-message -p -t "$pane_left" '#{pane_width}')" || width_left=0
    [ -n "$pane_right" ] && width_right="$(tmux display-message -p -t "$pane_right" '#{pane_width}')" || width_right=0

    border=0
    [ "$width_left" -gt 0 ] && border=$((border+1))
    [ "$width_right" -gt 0 ] && border=$((border+1))

        width_center="$(tmux display-message -t "$pane" -p '#{pane_width}')"
    width_all="$((width_center+width_left+width_right+border))"

    [ "$((width+5))" -ge "$width_all" ] && width="$width_all"
}

destroy() {
    [ -n "$pane_left" ] && [ "$pane_left" != "$TMUX_PANE" ] && tmux kill-pane -t "$pane_left"
    [ -n "$pane_right" ] && [ "$pane_right" != "$TMUX_PANE" ] && tmux kill-pane -t "$pane_right"
    [ "$pane_left" = "$TMUX_PANE" ] || [ "$pane_right" = "$TMUX_PANE" ] && tmux kill-pane -t "$TMUX_PANE"
    true
}


create() {
    width_left=$(( (width_all-width-2)/2 ))
    [ "$width_left" -gt 2 ] || width_left=2
    if [ -n "$pane_left" ]; then
        tmux resize-pane -t "$pane_left" -x "$width_left"
    else
        tmux split-window -hdbl "$width_left" -t "$pane" "$command_left"
    fi

        width_right=$(( width_all-width-width_left-2 ))
        [ "$width_right" -gt 2 ] || width_right=2
    if [ -n "$pane_right" ]; then
        tmux resize-pane -t "$pane_right" -x "$width_right"
    else
        tmux split-window -hdl "$width_right" -t "$pane" "$command_right"
    fi
}

equalize() {
        width="$width_center"
    [ "$width" -lt "$width_all" ] && create
}

verify() {
    tmux has-session -t "$pane" 2>/dev/null || destroy
}

resize() {
    width="$((width_center${1}))"
    if [ "$((width+5))" -ge "$width_all" ]; then
        destroy
    else
        create
    fi
}

main() {
    find_margins
    find_geometry
    case "$action" in
        sleep )
            trap destroy INT
            trap 'verify; kill "$!" 2>/dev/null; $todo &' WINCH
            while true; do
                $todo &
                while sleep 1; do verify; done
            done
        ;;
        ""|c* )
            create
        ;;
        d* )
            destroy
        ;;
        e* )
            equalize
        ;;
        + )
            resize "+$step"
        ;;
        - )
            resize "-step"
        ;;
        +*|-* )
            resize "$action"
        ;;
    esac
}

main

There are some fragments and solutions I'm not proud of. Still it works.


Use it like this (provided it's in a directory in your $PATH):

  • To create margins:
    • _tmux-margins c
    • or _tmux-margins create
    • or _tmux-margins cwhatever (in fact anything that starts with c)
    • or _tmux-margins (create is the default action).
  • To destroy margins:
    • _tmux-margins d (in fact anything that starts with d: del, delete, destroy, dwhatever).
  • To equalize width of margins:
    • _tmux-margins e (in fact anything that starts with e).
  • To enlarge the center pane (shrink margins):
    • _tmux-margins +
    • or _tmux-margins +N (add N columns to the center pane, if possible).
  • To shrink the center pane (enlarge margins):
    • _tmux-margins -
    • or _tmux-margins -N (subtract N columns from the center pane, if possible).

The second argument (not used in the above examples) specifies the pane. This way you can invoke the tool from another pane or from the outside of tmux. Examples:

_tmux-margins c .1
_tmux-margins - %5

There are two useful variables:

  • TMUX_M_COLUMNS – desired width of the center pane when margins are created (80 if unset).
  • TMUX_M_COMMAND – command to run in margin panes (true if unset). If a margin is resized, the command will be restarted. The main reason I implemented this:

    TMUX_M_COMMAND='cmatrix -u 20' _tmux-margins create
    

I have this in my ~/.tmux.conf:

bind-key -T root C-Up    run-shell -b 'TMUX_PANE="#{pane_id}" _tmux-margins d "#{pane_id}"'
bind-key -T root C-Down  run-shell -b 'TMUX_PANE="#{pane_id}" _tmux-margins c "#{pane_id}"'
bind-key -T root C-Left  run-shell -b 'TMUX_PANE="#{pane_id}" _tmux-margins - "#{pane_id}"'
bind-key -T root C-Right run-shell -b 'TMUX_PANE="#{pane_id}" _tmux-margins + "#{pane_id}"'

These lines make Ctrl+, Ctrl+, Ctrl+ and Ctrl+ invoke _tmux-margins d, … c, … - and … + respectively, targeted at the current pane.

Notes:

  • These key combinations work for me locally on Linux and via ssh from Linux to Linux. They don't work for me when I'm on Windows using PuTTY to connect to Linux. I have not attempted to fix this issue. Maybe it's possible to configure PuTTY so it works, I don't know. In any case you can choose other combinations, possibly with the prefix key (bind-key -T prefix …).
  • Go ahead and map _tmux-margins e as well, if you find it useful.
  • The script invoked with a keystroke has no access to the environment of the shell in the current pane (compare this answer). This means in order to use TMUX_M_COLUMNS or TMUX_M_COMMAND with keystrokes you need to set them for the tmux server itself:

    • for the very first tmux (the one that starts the server):

      TMUX_M_COMMAND='cmatrix -b' tmux
      
    • or in your ~/.tmux.conf:

      set-environment -g TMUX_M_COMMAND 'cmatrix -b'
      

      (note -g (global environment) is a must because tmux.conf is parsed before any session starts);

    • or later (when tmux server is running) like this:

      tmux set-environment TMUX_M_COMMAND 'cmatrix -b'
      

      (with or without -g; note if not inside tmux, then you need -g or -t);

    • or from the inside of tmux:

      prefix:set-environment TMUX_M_COMMAND 'cmatrix -b'Enter

      (again, with or without -g).


General notes:

  • Margins are just panes. If you split a margin or swap it with another pane or break it out of the window, so it doesn't look like a proper margin, then it's your problem. Or if you split the center pane etc.
  • On the other hand you can resize a margin like any other pane (possibly with a mouse); then _tmux-margins e is useful.
  • My script tries not to create margins for margins.
  • Destroying one margin destroys the other. You can select a margin and hit Ctrl+c instead of invoking _tmux-margins d.
  • Destroying a pane destroys its margins (if they exist).

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service, privacy policy and cookie policy

Not the answer you're looking for? Browse other questions tagged or ask your own question.