├── .gitignore ├── unified-titles-demo.gif ├── .editorconfig ├── bash-titles.plugin.sh ├── scripts ├── get_tzvt_config.sh ├── set_tmux_title.sh └── get_hoststring.py ├── plugin ├── get_zsh_named_dirs.zsh └── unified-titles.vim ├── LICENSE ├── defaults.sh ├── unified-titles.tmux ├── unified-titles.plugin.zsh └── README.md /.gitignore: -------------------------------------------------------------------------------- 1 | .DS_Store 2 | .*neomake* 3 | -------------------------------------------------------------------------------- /unified-titles-demo.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/MikeDacre/tmux-zsh-vim-titles/HEAD/unified-titles-demo.gif -------------------------------------------------------------------------------- /.editorconfig: -------------------------------------------------------------------------------- 1 | # EditorConfig is awesome: http://EditorConfig.org 2 | 3 | # top-most EditorConfig file 4 | root = true 5 | 6 | [*] 7 | charset = utf-8 8 | end_of_line = lf 9 | indent_size = 4 10 | indent_style = space 11 | insert_final_newline = true 12 | -------------------------------------------------------------------------------- /bash-titles.plugin.sh: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env bash 2 | 3 | [ -n "$HOSTNAME" ] || HOSTNAME=$(uname -n | sed 's/\..*//g') 4 | 5 | # Set title with PS1 to be 'b:' 6 | if [ -n "$TMUX" ]; then 7 | PS1="\ekb:${HOSTNAME}:\w\e\a$PS1" 8 | PS1="\033]0;b:${HOSTNAME}:\w\a$PS1" 9 | elif [[ $TERM =~ screen* ]]; then 10 | PS1="\ekb:${HOSTNAME}:\w\e\a$PS1" 11 | PS1="\033]0;b:${HOSTNAME}:\w\a$PS1" 12 | elif [[ $TERM =~ xterm* ]]; then 13 | PS1="\033]0;b:${HOSTNAME}:\w\a$PS1" 14 | elif [[ $TERM =~ ^rxvt-unicode.* ]]; then 15 | PS1="\33]2;b:${HOSTNAME}:\w\007$PS1" 16 | fi 17 | -------------------------------------------------------------------------------- /scripts/get_tzvt_config.sh: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env bash 2 | # Path to config file, to create one, just make a copy of this file 3 | # at your $tzvt_config location (e.g. ~/.tzvt_config 4 | if [ -z "$tzvt_config" ]; then 5 | if [ -n "$TMUX" ] && tmux ls >/dev/null 2>/dev/null; then 6 | tzvt_config=$(tmux show-option -gqv @tzvt_config | tr -d "[:space:]") 7 | fi 8 | if [ -z "$tzvt_config" ]; then 9 | if [ -f "$HOME/.tzvt_config" ]; then 10 | tzvf_config="$HOME/.tzvt_config" 11 | elif [ -f "${tzvt_tmux}/profile.sh" ]; then 12 | tzvt_config="${tzvt_tmux}/profile.sh" 13 | fi 14 | fi 15 | fi 16 | 17 | if [ -f "$tzvt_config" ]; then 18 | source "$tzvt_config" 19 | fi 20 | -------------------------------------------------------------------------------- /plugin/get_zsh_named_dirs.zsh: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env zsh 2 | 3 | main() { 4 | declare -i path_width 5 | 6 | local file=$1 7 | local path_width=$2 8 | local ZSH_BOOKMARKS=$3 9 | 10 | if [ -d "${file:A}" ]; then 11 | cd "${file:A}" 12 | elif [ -f "${file:a}" ]; then 13 | cd "$( dirname "${file:A}" )" 14 | fi 15 | 16 | if [ -n "$path_width" ]; then 17 | re='^[0-9]+$' 18 | if ! [[ $path_width =~ $re ]] ; then 19 | path_width=40 20 | fi 21 | else 22 | path_width=40 23 | fi 24 | 25 | [[ -n "$ZSH_BOOKMARKS" && -f "$ZSH_BOOKMARKS" ]] || ZSH_BOOKMARKS="$HOME/.zshbookmarks" 26 | if [ -f "$ZSH_BOOKMARKS" ]; then 27 | source "$ZSH_BOOKMARKS" 28 | fi 29 | 30 | i="%${path_width}<...<%~%<<" 31 | 32 | path=$(echo ${(%)i}) 33 | 34 | echo $path 35 | } 36 | main "$1" "$2" "$3" 37 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2018 Mike Dacre 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 | -------------------------------------------------------------------------------- /scripts/set_tmux_title.sh: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env bash 2 | # Actually set the tmux terminal title, not the window title 3 | # Inherits all variables from calling script, used by ZSH and 4 | # tmux plugin 5 | 6 | # Turn tmux titles on 7 | tmux set -g set-titles on 8 | 9 | # Is there a bell? 10 | if [ -n "$1" ] && [[ "$1" == 'bell' ]]; then 11 | BELL_STR='!' 12 | else 13 | BELL_STR='' 14 | fi 15 | 16 | # Update shell environment from variables 17 | update_env_from_tmux() { 18 | eval "$(tmux show-environment -s | grep -v "^unset")" 19 | } 20 | 21 | main() { 22 | # Update the shell environment 23 | update_env_from_tmux 24 | # Detect if we are in an SSH session, use script to munge tzvt_tmux_title_format_ssh 25 | # if we are, else just use the simple title format 26 | # Note: SSH_TTY and SSH_CLIENT remain set by children of tmux if tmux started by an 27 | # SSH session, SSH_CONNECTION is reset though. The below test allows us to know if 28 | # we are currently accessing the terminal via SSH or not. 29 | if [ -n "$SSH_CONNECTION" ] || [[ $(command ps -o comm= -p $PPID) =~ 'ssh' ]]; then 30 | tmux_string=$(tmux show-option -gqv @title-host-string) 31 | else 32 | tmux_string=$(tmux show-option -gqv @title-string) 33 | fi 34 | 35 | # Actually set the title 36 | tmux set -g set-titles-string "${BELL_STR}${tmux_string}" 37 | } 38 | main 39 | -------------------------------------------------------------------------------- /defaults.sh: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | # Default variables for tmux-zsh-vim-titles 3 | 4 | ############# 5 | # Control # 6 | ############# 7 | 8 | # Path to config file, to create one, just make a copy of this file 9 | # at your $tzvt_config location (e.g. ~/.tzvt_config 10 | [ -n "$tzvt_config" ] || tzvt_config="$HOME/.tzvt_config" 11 | 12 | # Update tmux title on zsh shell change 13 | [ -n "$tzvt_zsh_update_tmux" ] || tzvt_zsh_update_tmux=false 14 | 15 | # Update the window name also 16 | [ -n "$tzvt_set_tmux_window_status" ] || tzvt_set_tmux_window_status=false 17 | 18 | # Use more CPU intensive vim title change 19 | [ -n "$tzvt_vim_force_tmux_title_change" ] || tzvt_vim_force_tmux_title_change=false 20 | 21 | ############# 22 | # Formats # 23 | ############# 24 | 25 | ## Hostname, JSON dictionary, e.g.: 26 | ## tzvt_host_dict="{$HOST: 'mycomp'}" Used to replace hostname 27 | ## wherever host name is used 28 | [ -n "$tzvt_host_dict" ] || tzvt_host_dict="" 29 | 30 | # Space taken by the path in the title bar 31 | [ -n "$tzvt_pth_width" ] || tzvt_pth_width=60 32 | # Space taken by the path in the window tab, if tzvt_set_tmux_window_status is true 33 | [ -n "$tzvt_win_pth_width" ] || tzvt_win_pth_width=25 34 | 35 | ## Tmux 36 | [ -n "$tzvt_tmux_title_start" ] || tzvt_tmux_title_start='t:' 37 | [ -n "$tzvt_tmux_title_root" ] || tzvt_tmux_title_root='rt:' 38 | 39 | # tzvt_tmux_title_format_ssh is used on SSH, and is parsed to use 40 | # the shortest hostname possible. For the long hostname, use #H or set $HOSTNAME 41 | [ -n "$tzvt_tmux_title_format" ] || tzvt_tmux_title_format='#S:#T' 42 | [ -n "$tzvt_tmux_title_format_ssh" ] || tzvt_tmux_title_format_ssh='#h:#S:#T' 43 | 44 | # For window names, if tzvt_set_tmux_window_status is true, in tmux this is 45 | # altered such that #W is replaced with #T (terminal title). 46 | [ -n "$tzvt_tmux_win_current_fmt" ] || tzvt_tmux_win_current_fmt='#F#I:#W' 47 | [ -n "$tzvt_tmux_win_other_fmt" ] || tzvt_tmux_win_other_fmt='#F#I:#W' 48 | 49 | ## ZSH 50 | [ -n "$tzvt_zsh_title_fmt" ] || tzvt_zsh_title_fmt='${cmd}:${pth}' 51 | 52 | ## Vim 53 | [ -n "$tzvt_vim_title_prefix" ] || tzvt_vim_title_prefix="v:" 54 | # Include PATH in vim title: 55 | # true=path from current location 56 | # long=entire path, shortened by ZSH named dirs if run from ZSH 57 | # zsh=try to use ZSH named dirs if ZSH installed, fallback to no path 58 | # false=do not include a PATH, the default (it's cleaner) 59 | [ -n "$tzvt_vim_include_path" ] || tzvt_vim_include_path=false 60 | -------------------------------------------------------------------------------- /scripts/get_hoststring.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python 2 | # -*- coding: utf-8 -*- 3 | """ 4 | Get hostname, convert using $tzvt_host_dict if set. 5 | 6 | $tzvt_host_dict should have a json style dictionary. 7 | """ 8 | import os 9 | import sys 10 | import json 11 | import subprocess as sub 12 | import shlex as sh 13 | 14 | 15 | def run(cmd, shell=False, check=False, get='all'): 16 | """Replicate getstatusoutput from subprocess. 17 | 18 | Params 19 | ------ 20 | cmd : str or list 21 | shell : bool, optional 22 | Run as a shell, allows piping 23 | check : bool, optional 24 | Raise exception if command failed 25 | get : {'all', 'code', 'stdout', 'stderr'}, optional 26 | Control what is returned: 27 | - all: (code, stdout, stderr) 28 | - code/stdout/stderr: only that item 29 | - None: code only 30 | 31 | Returns 32 | ------- 33 | output : str or tuple 34 | See get above. Default return value: (code, stdout, stderr) 35 | """ 36 | get_options = ['all', 'stdout', 'stderr', 'code', None] 37 | if get not in get_options: 38 | raise ValueError( 39 | 'get must be one of {0} is {1}'.format(get_options, get) 40 | ) 41 | if not shell and isinstance(cmd, str): 42 | cmd = sh.split(cmd) 43 | if get != 'code' and get is not None: 44 | pp = sub.Popen(cmd, shell=shell, stdout=sub.PIPE, stderr=sub.PIPE) 45 | out, err = pp.communicate() 46 | else: 47 | pp = sub.Popen(cmd, shell=shell) 48 | pp.communicate() 49 | if not isinstance(out, str): 50 | out = out.decode() 51 | if not isinstance(err, str): 52 | err = err.decode() 53 | code = pp.returncode 54 | if check and code != 0: 55 | if get: 56 | sys.stderr.write( 57 | 'Command failed\nSTDOUT:\n{0}\nSTDERR:\n{1}\n' 58 | .format(out, err) 59 | ) 60 | raise sub.CalledProcessError(code, cmd) 61 | if get == 'all': 62 | return code, out.rstrip(), err.rstrip() 63 | elif get == 'stdout': 64 | return out.rstrip() 65 | elif get == 'stderr': 66 | return err.rstrip() 67 | return code 68 | 69 | 70 | def get_hostname(): 71 | """Get the hostname itself.""" 72 | host_var = 'tzvt_host_dict' 73 | s = os.environ.get(host_var) if host_var in os.environ else '{}' 74 | s = s.replace("'", '"') 75 | 76 | try: 77 | host_dict = json.loads(s) 78 | except ValueError as err: 79 | sys.stderr.write( 80 | 'Failed to parse {0} with the error:\n{1}\nNo hosts loaded\n' 81 | .format(host_var, str(err)) 82 | ) 83 | host_dict = {} 84 | 85 | if 'HOSTSHORT' in os.environ: 86 | host = os.environ.get('HOSTSHORT').split('.')[0] 87 | elif 'HOSTNAME' in os.environ: 88 | host = os.environ.get('HOSTNAME').split('.')[0] 89 | elif 'HOST' in os.environ: 90 | host = os.environ.get('HOST').split('.')[0] 91 | else: 92 | host = run('uname -n', get='stdout') 93 | 94 | host = host.strip() 95 | host = host_dict[host] if host in host_dict else host 96 | return host.strip() 97 | 98 | 99 | def main(): 100 | """Get host string for title.""" 101 | host_str_var = 'tzvt_tmux_title_format_ssh' 102 | if len(sys.argv) > 1 and '--host-only' in sys.argv: 103 | return get_hostname() 104 | h = os.environ.get(host_str_var) if host_str_var in os.environ else '#h:#S:#T' 105 | if '#h' in h: 106 | host_str = h.replace('#h', get_hostname()) 107 | else: 108 | host_str = h 109 | return host_str 110 | 111 | 112 | if __name__ == '__main__' and '__file__' in globals(): 113 | host_string = main() 114 | if not host_string: 115 | sys.exit(1) 116 | sys.stdout.write(host_string) 117 | sys.exit(0) 118 | -------------------------------------------------------------------------------- /unified-titles.tmux: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env bash 2 | 3 | CURRENT_DIR="$( cd "$( dirname "${BASH_SOURCE[0]}" )" && pwd )" 4 | 5 | # Attempt to get all applicable profiles as tmux runs this code 6 | # with no environment 7 | if [ -f "$HOME/.bashrc" ]; then 8 | source "$HOME/.bashrc" 2>/dev/null >/dev/null 9 | fi 10 | if [ -f "$HOME/.profile" ]; then 11 | source "$HOME/.profile" 2>/dev/null >/dev/null 12 | fi 13 | 14 | # Get the user config, if it exists (defaults to ~/.tzvt_config) 15 | . "$CURRENT_DIR/scripts/get_tzvt_config.sh" 16 | 17 | # Get default starting strings, no set variables overwritten 18 | # shellcheck source=defaults.sh 19 | . "$CURRENT_DIR/defaults.sh" 20 | 21 | _init_tzvq() { 22 | # Add variables to the tmux environment 23 | declare -a all_vars 24 | all_vars=("DISPLAY" "SSH_CONNECTION" "tzvt_set_tmux_window_status" "tzvt_vim_force_tmux_title_change" \ 25 | "tzvt_tmux_title_start" "tzvt_tmux_title_root" "tzvt_tmux_title_format" "tzvt_tmux_title_format_ssh" \ 26 | "tzvt_tmux_win_current_fmt" "tzvt_tmux_win_other_fmt") 27 | # Get existing tmux update-environment variables 28 | variables="$(tmux show-option -g update-environment | sed 's/.*] "\([^"]\+\)"/\1/' | xargs echo -n)" 29 | # Add ours only if they do not already exist 30 | for var in "${all_vars[@]}"; do 31 | if [[ ! "$variables" =~ $var ]]; then 32 | variables+=" $var" 33 | fi 34 | done 35 | # Set them now, they will be updated in the shell by sourcing 36 | # scripts/set_tmux_title.sh 37 | tmux set -g update-environment "${variables}" 38 | 39 | # Make a note that we have run 40 | tmux set -gq @tzvt_initialized true 41 | } 42 | 43 | main() { 44 | if [[ ! $(tmux show-option -gqv tzvt_initialized) == true ]]; then 45 | _init_tzvq 46 | fi 47 | 48 | # Turn tmux titles on 49 | tmux set -g set-titles on 50 | 51 | if [[ "${USER}" == 'root' ]]; then 52 | tmux_start_str="${tzvt_tmux_title_root}" 53 | else 54 | tmux_start_str="${tzvt_tmux_title_start}" 55 | fi 56 | 57 | local host_string 58 | local host_sep='#h' 59 | # Munge the host string just once, but only if necessary 60 | if [[ $tzvt_tmux_title_format_ssh =~ $host_sep ]]; then 61 | if [ -n "$tzvt_host_dict" ]; then 62 | echo "$tzvt_host_dict" 63 | host_string="$($CURRENT_DIR/scripts/get_hoststring.py | tr -d "[:space:]"):" 64 | elif [ -n "$HOSTSHORT" ]; then 65 | host_string="${tzvt_tmux_title_format_ssh/$host_sep/$HOSTSHORT}" 66 | elif [ -n "$HOSTNAME" ]; then 67 | host_string="${tzvt_tmux_title_format_ssh/$host_sep/$HOSTNAME}" 68 | else 69 | host_string="${tzvt_tmux_title_format_ssh/$host_sep/$HOST}" 70 | fi 71 | else 72 | host_string="$tzvt_tmux_title_format_ssh" 73 | fi 74 | 75 | # We use tmux_string if we are local, and tmux_host_string if we are on ssh 76 | tmux_string="#{window_bell_flag,!,}${tmux_start_str}${tzvt_tmux_title_format}" 77 | tmux_host_string="#{window_bell_flag,!,}${tmux_start_str}${host_string}" 78 | 79 | # Set the title string in tmux 80 | tmux set -g @title-string "${tmux_string}" 81 | tmux set -g @title-host-string "${tmux_host_string}" 82 | 83 | # Actually set the titles, also run by ZSH 84 | # shellcheck source=scripts/set_tmux_title.sh 85 | . "$CURRENT_DIR/scripts/set_tmux_title.sh" 86 | 87 | # Update window name if requested 88 | if $tzvt_set_tmux_window_status; then 89 | # Only globally set the widow-current-status-format once, as it is modified 90 | # by other apps 91 | update_win=$(tmux show-option -gqv @win-status-set) 92 | if [[ "$update_win" != 'true' ]]; then 93 | tmux set-window-option -g window-status-current-format "${tzvt_tmux_win_current_fmt}" 94 | tmux set-window-option -g automatic-rename-format "${tzvt_tmux_win_other_fmt}" 95 | tmux set-window-option -g automatic-rename on 96 | tmux set-option -gq @win-status-set 'true' 97 | fi 98 | # Update the other format periodically 99 | tmux set-window-option -g window-status-format "${tzvt_tmux_win_other_fmt}" 100 | fi 101 | } 102 | main 103 | 104 | # Update string on attach and detatch 105 | tmux set-hook -g alert-bell "run \"$CURRENT_DIR/scripts/set_tmux_title.sh bell\"" 106 | tmux set-hook -g client-attached "run \"$CURRENT_DIR/unified-titles.tmux\"" 107 | tmux set-hook -g client-detached "run \"echo -ne \\\"\e]0;$HOSTNAME\a\\\"\"" 108 | -------------------------------------------------------------------------------- /unified-titles.plugin.zsh: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env zsh 2 | # Set the ZSH component of the title string 3 | # This component is *heavily* inspired by https://github.com/jreese/zsh-titles 4 | 5 | CURRENT_DIR="$(dirname $0:A)" 6 | 7 | [ -n "$TMUX" ] && tmux ls >/dev/null 2>/dev/null && in_tmux=true || in_tmux=false 8 | 9 | # Get the user config, if it exists (defaults to ~/.tzvt_config) 10 | . "$CURRENT_DIR/scripts/get_tzvt_config.sh" 11 | 12 | # Get default starting strings, no existing variables overwritten. 13 | # shellcheck source=defaults.sh 14 | . "$CURRENT_DIR/defaults.sh" 15 | 16 | # Check if we want to update tmux 17 | if $in_tmux && [[ $tzvt_zsh_update_tmux == true ]] && [[ $(tmux show-option -gqv tzvt_initialized) == true ]]; then 18 | # Check if plugin installed 19 | t_plug=true 20 | else 21 | t_plug=false 22 | fi 23 | 24 | # Run the tmux title setting plugin on shell start 25 | TITLE_PRE="" 26 | if ! $in_tmux; then 27 | if [ -n "$SSH_CONNECTION" ] || [[ $(command ps -o comm= -p $PPID) =~ 'ssh' ]]; then 28 | if [ -n "$tzvt_host_dict" ]; then 29 | TITLE_PRE="$($CURRENT_DIR/scripts/get_hoststring.py --host-only | tr -d "[:space:]"):" 30 | elif [ -n "$HOSTSHORT" ]; then 31 | TITLE_PRE="${HOSTSHORT}:" 32 | elif [ -n "$HOSTNAME" ]; then 33 | TITLE_PRE="${HOSTNAME}:" 34 | else 35 | TITLE_PRE="${HOST}:" 36 | fi 37 | fi 38 | fi 39 | 40 | # Set titles 41 | function update_title() { 42 | local cmd 43 | local pth 44 | # escape '%' in $1, make nonprintables visible 45 | cmd=${(V)1//\%/\%\%} 46 | # remove newlines 47 | cmd=${cmd//$'\n'/} 48 | pth="%${tzvt_pth_width}<...<%~" 49 | short_pth="%${tzvt_win_pth_width}<...<%~" 50 | 51 | # Set core titles 52 | if [ -n "$cmd" ]; then 53 | TITLE=$(eval echo "${tzvt_zsh_title_fmt}") 54 | SHORT_TITLE=$(eval echo "${tzvt_zsh_title_fmt/$'pth'/short_pth}") 55 | else 56 | TITLE=${pth} 57 | SHORT_TITLE=${short_pth} 58 | fi 59 | 60 | # Add host to title if necessary 61 | if [ -n "$TITLE_PRE" ] && [[ ! "$TITLE" =~ "$TITLE_PRE" ]]; then 62 | TITLE="${TITLE_PRE}${TITLE}" 63 | fi 64 | 65 | ## Terminal title (work even if ssh from tmux) 66 | 67 | # Emulator dependent, results in double title set with knosole 68 | if [ -n "$KONSOLE_DBUS_SERVICE" ]; then 69 | print -Pn "\033]30;${(%)TITLE}\007" 70 | fi 71 | 72 | # Term dependent 73 | if $in_tmux; then 74 | # print -Pn "\ek${(%)TITLE}\e\\" # Sets window name 75 | print -Pn "\e]0;${(%)TITLE}\a" 76 | elif [[ "$TERM" =~ screen* ]] || [[ "$TERM" =~ xterm* ]]; then 77 | print -Pn "\e]0;${(%)TITLE}\a" 78 | elif [[ "$TERM" =~ ^rxvt-unicode.* ]]; then 79 | printf '\33]2;%s\007' ${(%)TITLE} 80 | fi 81 | 82 | # Tmux Specific Stuff 83 | if $in_tmux; then 84 | # Reset tmux portion of the title if plugin is installed 85 | # Run as source to preserve current environment 86 | if $t_plug; then 87 | # shellcheck source=scripts/set_tmux_title.sh 88 | . $CURRENT_DIR/scripts/set_tmux_title.sh 89 | fi 90 | 91 | # Tmux Window Title 92 | if [ -n "$tzvt_set_tmux_window_status" ]; then 93 | # Only set the current window format globally once, as it is overriden elsewhere 94 | tmux set-window-option window-status-current-format "${tzvt_tmux_win_current_fmt}" 95 | 96 | # Window title is short path 97 | # print -Pn "\ek${(%)SHORT_TITLE}\e\\" # Sets window name 98 | tmux rename-window "${(%)SHORT_TITLE}" 99 | fi 100 | fi 101 | } 102 | 103 | # Called just before a command is executed 104 | # Title component will the command and the path 105 | function _zsh_title__preexec() { 106 | local -a cmd 107 | 108 | # Re-parse the command line 109 | cmd=(${(z)@}) 110 | 111 | # Construct a command that will output the desired job number. 112 | case $cmd[1] in 113 | fg) cmd="${(z)jobtexts[${(Q)cmd[2]:-%+}]}" ;; 114 | %*) cmd="${(z)jobtexts[${(Q)cmd[1]:-%+}]}" ;; 115 | esac 116 | 117 | cmd="${cmd[1]}" 118 | 119 | # Escape '\' 120 | cmd="${cmd//\\/\\\\\\\\}" 121 | 122 | # Strip start and end whitespace 123 | cmd=$(echo "${cmd}" | sed -e 's/^[[:space:]]*//' -e 's/[[:space:]]*$//') 124 | 125 | update_title "${cmd}" 126 | } 127 | 128 | # Called just before the prompt is printed 129 | # Title component will just be the path 130 | function _zsh_title__precmd() { 131 | update_title 132 | } 133 | 134 | autoload -Uz add-zsh-hook 135 | add-zsh-hook precmd _zsh_title__precmd 136 | add-zsh-hook preexec _zsh_title__preexec 137 | 138 | # Run once as the prompt is loading 139 | update_title 140 | -------------------------------------------------------------------------------- /plugin/unified-titles.vim: -------------------------------------------------------------------------------- 1 | """"""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""" 2 | " Set Title by Buffer " 3 | """"""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""" 4 | 5 | """"""""""""""""""""""""""""""" 6 | " Check and Get Environment " 7 | """"""""""""""""""""""""""""""" 8 | 9 | " Get path to self 10 | let s:path = fnamemodify(resolve(expand(':p')), ':h') 11 | 12 | " Source shell profiles 13 | call system('cd $(dirname ' . s:path . '); source scripts/get_tzvt_config.sh') 14 | call system('cd $(dirname ' . s:path . '); source defaults.sh') 15 | 16 | " Get Variables 17 | if !exists("g:tzvt_vim_title_prefix") 18 | let g:tzvt_vim_title_prefix = system('[ -n "$tzvt_vim_title_prefix" ] || tzvt_vim_title_prefix="v:"; echo -n "${tzvt_vim_title_prefix}" | tr -d "[:space:]"') 19 | endif 20 | if !exists("g:tzvt_vim_force_tmux_title_change") 21 | let g:tzvt_vim_force_tmux_title_change = system('[ -n "$tzvt_vim_force_tmux_title_change" ] && $tmux_force_tmux_title_change && echo -n 1 || echo -n 0 | tr -d "[:space:]"') 22 | endif 23 | if !exists("g:tzvt_set_tmux_window_status") 24 | let g:tzvt_set_tmux_window_status = system('[ -n "$tzvt_set_tmux_window_status" ] && $tzvt_set_tmux_window_status && echo -n 1 || echo -n 0 | tr -d "[:space:]"') 25 | endif 26 | if !exists("g:tzvt_vim_include_path") 27 | let g:tzvt_vim_include_path = system('if [ -n "$tzvt_vim_include_path" ]; then if [[ $tzvt_vim_include_path == "long" ]]; then echo -n long; elif [[ $tzvt_vim_include_path == "zsh" ]]; then echo -n zsh; elif [[ $tzvt_vim_include_path == "true" ]]; then echo -n 1; else echo -n 0; fi; else echo -n 0; fi | tr -d "[:space:]"') 28 | endif 29 | if !exists("g:tzvt_vim_pth_width") 30 | let g:tzvt_vim_pth_width = system('[ -n "$tzvt_win_pth_width" ] || tzvt_win_pth_width=40; echo -n "$tzvt_win_pth_width" | tr -d "[:space:]"') 31 | endif 32 | if !exists("g:tzvt_vim_path_before") 33 | let g:tzvt_vim_pth_width = system('[ -n "$tzvt_vim_path_before" ] && [[ "$tzvt_vim_path_before" == true ]] && echo 1 || echo 0 | tr -d "[:space:]"') 34 | endif 35 | let s:zsh_bookmarks = system('[ -n "$ZSH_BOOKMARKS" ] || ZSH_BOOKMARKS="$HOME/.zshbookmarks"; echo -n "${ZSH_BOOKMARKS}" | tr -d "[:space:]"') 36 | let s:has_tmux = system('[ -n "$TMUX" ] && tmux ls >/dev/null 2>/dev/null && echo -n 1 || echo -n 0 | tr -d "[:space:]"') 37 | let s:has_zsh = system('hash zsh 2>/dev/null && echo -n 1 || echo -n 0 | tr -d "[:space:]"') 38 | 39 | " Get window format and update to terminal title if window status update 40 | if g:tzvt_set_tmux_window_status 41 | let win_orig_status = system('[ -n "$tzvt_tmux_win_current_fmt" ] || tzvt_tmux_win_current_fmt="#I:#W#F" ; echo -n $tzvt_tmux_win_current_fmt') 42 | " We use the terminal title in both the terminal and status line in 43 | " vim only, so substitute window name (#W) for terminal title (#T) 44 | let win_vim_status = substitute(win_orig_status, '#W', '#T', 'g') 45 | endif 46 | 47 | """"""""""""""" 48 | " Functions " 49 | """"""""""""""" 50 | 51 | " Function to get named directory in zsh 52 | function! ZSHDirs() 53 | let g:dir_path = system('zsh ' . s:path . '/get_zsh_named_dirs.zsh ' . expand("%:~:h") . ' ' . g:tzvt_vim_pth_width . ' ' . s:zsh_bookmarks) 54 | endfunction 55 | 56 | " Tmux Specific Functions 57 | if g:tzvt_set_tmux_window_status || g:tzvt_vim_force_tmux_title_change 58 | 59 | " Override the terminal title if vim messes up 60 | function! SetTmuxTerminalTitle(titleString) 61 | let cmd2 = 'silent !echo -n -e "\033]0;' . a:titleString . '\007"' 62 | let cmd3 = 'silent !echo -n -e "\033k' . a:titleString . '\007"' 63 | execute cmd2 64 | execute cmd3 65 | redraw! 66 | endfunction 67 | 68 | " Set the window name and format 69 | function! SetTmuxWindowTitleFormat(titleString, titleFormat) 70 | " While on the current window, we will use the terminal title 71 | call system('tmux set window-status-current-format ' . shellescape(a:titleFormat)) 72 | " While on other windows we will still use the name 73 | call SetTmuxWindowTitle(a:titleString) 74 | endfunction 75 | 76 | " Set the window name only 77 | function! SetTmuxWindowTitle(titleString) 78 | call system("tmux rename-window " . a:titleString) 79 | endfunction 80 | 81 | " Reset the window name to how it was before 82 | function! ResetTmuxWindowTitle(titleFormat) 83 | call SetTmuxWindowTitleFormat("", a:titleFormat) 84 | call system("tmux automatic-rename on") 85 | endfunction 86 | 87 | " Try to implement the '+' if file is modified. 88 | " Kinda works 89 | function! GetModStr() 90 | if &modified 91 | return '+' 92 | endif 93 | return '' 94 | endfunction 95 | 96 | " Handle special characters 97 | let my_asciictrl = nr2char(127) 98 | let my_unisubst = "␡" 99 | for i in range(1, 31) 100 | let my_asciictrl .= nr2char(i) 101 | let my_unisubst .= nr2char(0x2400 + i, 1) 102 | endfor 103 | 104 | " Set the initial title. Initial title has no modStr. 105 | let simpleTitle = g:tzvt_vim_title_prefix . tr(expand("%:t"), my_asciictrl, my_unisubst) 106 | endif 107 | 108 | 109 | """""""""""""""""""""""""""""" 110 | " Set Basic Title Settings " 111 | """""""""""""""""""""""""""""" 112 | 113 | " Start fresh 114 | set notitle 115 | 116 | " Set tmux control chars 117 | if !s:has_tmux 118 | if &term == "screen" || &term == "screen-256color" 119 | set t_ts=]0; 120 | set t_fs= 121 | elseif &term == 'nvim' 122 | set t_ts=k 123 | set t_fs= 124 | endif 125 | endif 126 | 127 | """"""""""""""""""" 128 | " Set the Title " 129 | """"""""""""""""""" 130 | 131 | " Decide which title to use 132 | if g:tzvt_vim_include_path == '0' || g:tzvt_vim_include_path == '0' 133 | let s:title_type = 'simple' 134 | elseif g:tzvt_vim_include_path == 'long' 135 | if $SHELL =~ 'zsh' 136 | let s:title_type = 'zsh' 137 | else 138 | let s:title_type = 'long' 139 | endif 140 | elseif g:tzvt_vim_include_path == 'zsh' 141 | if $SHELL =~ 'zsh' || g:has_zsh == '1' 142 | let s:title_type = 'zsh' 143 | else 144 | let s:title_type = 'simple' 145 | endif 146 | elseif g:tzvt_vim_include_path == 1 || g:tzvt_vim_include_path == '1' 147 | let s:title_type = 'short' 148 | else 149 | let s:title_type = 'simple' 150 | endif 151 | 152 | " Actually set the terminal title 153 | if s:title_type == 'simple' 154 | set title titlestring=%{g:tzvt_vim_title_prefix}%(%{expand(\"%:t\")}%)%(\ %M%) 155 | elseif s:title_type == 'zsh' 156 | call ZSHDirs() 157 | augroup zshPath 158 | au! 159 | autocmd BufEnter,BufNewFile,TabEnter,WinEnter * call ZSHDirs() 160 | augroup END 161 | if g:tzvt_vim_path_before 162 | set title titlestring=%{g:tzvt_vim_title_prefix}%{g:dir_path}:%(%{expand(\"%:t\")}%)%(\ %M%) 163 | else 164 | set title titlestring=%{g:tzvt_vim_title_prefix}%(%{expand(\"%:t\")}%):%{g:dir_path}%(\ %M%) 165 | endif 166 | elseif s:title_type == 'long' 167 | if g:tzvt_vim_path_before 168 | set title titlestring=%{g:tzvt_vim_title_prefix}%(%{expand(\"%:~:p:t\")}%)%(\ %M%) 169 | else 170 | set title titlestring=%{g:tzvt_vim_title_prefix}%(%{expand(\"%:t\")}%):%(%{expand(\"%:~:h\")}%)%(\ %M%) 171 | endif 172 | elseif s:title_type == 'short' 173 | if g:tzvt_vim_path_before 174 | set title titlestring=%{g:tzvt_vim_title_prefix}%(%{expand(\"%:~:.:p:t\")}%)%(\ %M%) 175 | else 176 | set title titlestring=%{g:tzvt_vim_title_prefix}%(%{expand(\"%:t\")}%):%(%{expand(\"%:~:.:h\")}%)%(\ %M%) 177 | endif 178 | endif 179 | 180 | " If requested set the initial window name 181 | if g:tzvt_set_tmux_window_status 182 | if s:has_tmux || &term == "screen" || &term == "screen-256color" 183 | call SetTmuxWindowTitleFormat(simpleTitle, win_vim_status) 184 | endif 185 | endif 186 | 187 | """""""""""""""""""""""""""""" 188 | " Manage Tmux Window Names " 189 | """""""""""""""""""""""""""""" 190 | 191 | " Use autocommands if the user wants to also update the tmux status window name 192 | " or if the simple titlestring setting does not work. 193 | if g:tzvt_set_tmux_window_status || g:tzvt_vim_force_tmux_title_change 194 | augroup termTitle 195 | " Clear prior commands in termTitle 196 | au! 197 | " Only do anything if we are in tmux 198 | if s:has_tmux || &term == "screen" || &term == "screen-256color" 199 | autocmd BufEnter,BufLeave,BufWritePost,FileWritePost,InsertEnter,TabEnter,WinEnter * let modStr = GetModStr() 200 | autocmd BufEnter,BufLeave,BufWritePost,FileWritePost,InsertEnter,TabEnter,WinEnter * let simpleTitle = g:tzvt_vim_title_prefix . tr(expand("%:t"), my_asciictrl, my_unisubst) . modStr 201 | " Set window titles 202 | if g:tzvt_set_tmux_window_status 203 | autocmd BufEnter,BufLeave,BufWritePost,FileWritePost,InsertEnter,TabEnter,WinEnter * call SetTmuxWindowTitle(simpleTitle) 204 | autocmd VimLeave * call ResetTmuxWindowTitle(win_orig_status) 205 | endif 206 | " Set title using terminal commands 207 | if g:tzvt_vim_force_tmux_title_change 208 | autocmd BufEnter,BufWritePost,TabEnter,WinEnter * call SetTmuxTerminalTitle(simpleTitle) 209 | endif 210 | " Clear title on leaving vim 211 | autocmd VimLeave * set t_ts=k\ 212 | endif 213 | augroup END 214 | endif 215 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # Unified Tmux-ZSH-Vim Terminal Titles 2 | 3 | 4 | Creates intelligent terminal titles in tmux, zsh, and vim, that work together 5 | to give information about session, ssh host, path, and currently edited vim 6 | buffer. Each part is modular and must be installed separately. 7 | 8 | Note: the ZSH component of this is *heavily* influenced by 9 | [jreese/zsh-titles](https://github.com/jreese/zsh-titles). The other components 10 | are inspired by a variety of stack exchange answers. 11 | 12 | ## How it works 13 | 14 | The three components of this plugin work together to make a useful 15 | colon-separated title for any combination of tmux, zsh, and vim. For example, if 16 | you are in a tmux session on your home computer with zsh at your home directory, 17 | your title will be `t:0:~`, if your tmux session was named fred, it would be 18 | `t:fred:~`. If you then opened `hi.txt` in vim or nvim, it would be 19 | `t:fred:v:hi.txt`. 20 | 21 | If by contrast, your did those same things over ssh on your remote machine 22 | hubble, those would be: `t:hubble:0:~`, `t:hubble:fred:~`, `t:hubble:v:hi.txt` 23 | instead. If you were the root user on hubble, the starting `t:` would instead be 24 | `rt:`. 25 | 26 | **Note:** A tmux terminal bell results in the title being prefixed with '!' 27 | until an update event (doing something in vim, pressing return in zsh, waiting 28 | for status-interval seconds (default 15)). 29 | 30 | ![tmux-zsh-vim-titles-demo](./unified-titles-demo.gif) 31 | 32 | ## Installation 33 | 34 | ### Tmux 35 | 36 | Install with [tpm](https://github.com/tmux-plugins/tpm) by adding the following 37 | line to your `.tmux.conf`: 38 | 39 | ``` 40 | set -g set-titles on 41 | set -g @plugin 'MikeDacre/tmux-zsh-vim-titles' 42 | ``` 43 | 44 | You will then need to source your tmux config (`tmux source ~/.tmux.conf`) and 45 | install the plugin by pressing your prefix key combo (defaults to Ctrl+b) 46 | followed by `I` (shift+i). You can update by running your prefix followed by 47 | `U`. 48 | 49 | ### ZSH 50 | 51 | The easiest way to install this plugin with ZSH is to use 52 | [Antigen](https://github.com/zsh-users/antigen) by adding the following line to 53 | the apprpriate spot in your `~/.zshrc` file: 54 | 55 | ``` 56 | antigen bundle MikeDacre/tmux-zsh-vim-titles 57 | ``` 58 | 59 | You will then need to reload your zsh configuration (e.g. by starting a new 60 | shell or sourcing your `~/.zshrc` to install the plugin. You can update by 61 | running `antigen upgrade`. 62 | 63 | Alternatively, if you use 64 | [oh-my-zsh](https://github.com/robbyrussell/oh-my-zsh), you can clone this into 65 | your `oh-my-zsh` custom plugin directory: 66 | 67 | 1. `mkdir -p ${ZSH}/custom/plugins` 68 | 2. `cd ${ZSH}/custom/plugins` 69 | 3. `git clone https://github.com/MikeDacre/tmux-zsh-vim-titles.git` 70 | 4. Add `plugins+=(unified-titles)` to the right spot in your `~/.zshrc` and 71 | reloading zsh 72 | 73 | To update you have to `cd` to the plugin directory and run `git pull`. 74 | 75 | ### Vim/NVIM 76 | 77 | There are a great many plugin managers for vim/nvim right now, I personally use 78 | [vim-plug](https://github.com/junegunn/vim-plug), to install with that manager, 79 | just add the following line to the right spot in your `~/.vimrc` or 80 | `~/nvim/init.vim`: 81 | 82 | ``` 83 | Plug 'MikeDacre/tmux-zsh-vim-titles' 84 | ``` 85 | 86 | You will then need to open a vim/nvim instance and run `PlugInstall` to install 87 | the plugin. To update run `PlugUpdate`. The various other plugin managers work 88 | similarly. 89 | 90 | ### Bash or another sh shell 91 | 92 | If you also use a non-zsh shell, you can source the `bash-titles.plugin.sh` file 93 | from your `~/.bashrc`. It doesn't do anywhere near as much as the ZSH version, 94 | it simply sets the terminal title to the path, avoiding the otherwise long 95 | titles that bash sometimes sets. 96 | 97 | If anyone wants to port the zsh plugin to bash, that would be awesome. It should 98 | be pretty easy, but I can't be bothered as I so rarely use bash. 99 | 100 | ## Configuration 101 | 102 | The plugins will work right out of the box, but the formats can be configured 103 | with a variety of shell variables. For example you could change the tmux prompt, 104 | disable setting the window tab names, or change the delimiter from `:` to 105 | something else. 106 | 107 | All variables can be set in the shell, and are specified in the `defaults.sh` 108 | config file. Vim variables may also be set in your vimrc, but must be set before 109 | the plugin is loaded, not after. All variables start with `tzvt_`, plugin 110 | specific variables are followed by the component name, e.g. `tzvt_vim_