diff --git a/tmux/tmux-config/.gitattributes b/tmux/tmux-config/.gitattributes new file mode 100644 index 0000000..4c9e44d --- /dev/null +++ b/tmux/tmux-config/.gitattributes @@ -0,0 +1,43 @@ +# common settings that generally should always be used with your language specific settings + +# Auto detect text files and perform LF normalization +# http://davidlaing.com/2012/09/19/customise-your-gitattributes-to-become-a-git-ninja/ +* text=auto + +# +# The above will handle all files NOT found below +# + +# Documents +*.doc diff=astextplain +*.DOC diff=astextplain +*.docx diff=astextplain +*.DOCX diff=astextplain +*.dot diff=astextplain +*.DOT diff=astextplain +*.pdf diff=astextplain +*.PDF diff=astextplain +*.rtf diff=astextplain +*.RTF diff=astextplain +*.md text +*.adoc text +*.textile text +*.mustache text +*.csv text +*.tab text +*.tsv text +*.sql text + +# Graphics +*.png binary +*.jpg binary +*.jpeg binary +*.gif binary +*.tif binary +*.tiff binary +*.ico binary +# SVG treated as an asset (binary) by default. If you want to treat it as text, +# comment-out the following line and uncomment the line after. +*.svg binary +#*.svg text +*.eps binary \ No newline at end of file diff --git a/tmux/tmux-config/.gitignore b/tmux/tmux-config/.gitignore new file mode 100644 index 0000000..4fe6511 --- /dev/null +++ b/tmux/tmux-config/.gitignore @@ -0,0 +1,28 @@ +### https://raw.github.com/github/gitignore/8ab86f6bb71e85b5046f1d921bbbe5ceec9063ba/Global/OSX.gitignore + +.DS_Store +.AppleDouble +.LSOverride + +# Icon must end with two \r +Icon + +# Thumbnails +._* + +# Files that might appear in the root of a volume +.DocumentRevisions-V100 +.fseventsd +.Spotlight-V100 +.TemporaryItems +.Trashes +.VolumeIcon.icns + +# Directories potentially created on remote AFP share +.AppleDB +.AppleDesktop +Network Trash Folder +Temporary Items +.apdisk + + diff --git a/tmux/tmux-config/Dockerfile b/tmux/tmux-config/Dockerfile new file mode 100644 index 0000000..706e022 --- /dev/null +++ b/tmux/tmux-config/Dockerfile @@ -0,0 +1,46 @@ +FROM debian:stretch-slim + +RUN apt-get update \ + && apt-get install -y --no-install-recommends \ + ca-certificates \ + fontconfig \ + git \ + gcc \ + iputils-ping \ + libevent-dev \ + libncurses-dev \ + locales \ + make \ + procps \ + wget \ + && wget -O - https://github.com/tmux/tmux/releases/download/2.6/tmux-2.6.tar.gz | tar xzf - \ + && cd tmux-2.6 \ + && LDFLAGS="-L/usr/local/lib -Wl,-rpath=/usr/local/lib" ./configure --prefix=/usr/local \ + && make \ + && make install \ + && cd .. \ + && rm -rf tmux-2.6 \ + && apt-get purge -y gcc make \ + && apt-get -y autoremove \ + && apt-get clean \ + && rm -rf /var/lib/apt/lists/* + +RUN sed -i -e 's/# en_US.UTF-8 UTF-8/en_US.UTF-8 UTF-8/' /etc/locale.gen \ + && locale-gen +ENV LC_ALL=en_US.UTF-8 +ENV LANG=en_US.UTF-8 + +RUN mkdir -p $HOME/.fonts $HOME/.config/fontconfig/conf.d \ + && wget -P $HOME/.fonts https://github.com/powerline/powerline/raw/develop/font/PowerlineSymbols.otf \ + && wget -P $HOME/.config/fontconfig/conf.d/ https://github.com/powerline/powerline/raw/develop/font/10-powerline-symbols.conf \ + && fc-cache -vf $HOME/.fonts/ + +WORKDIR /root + +RUN git clone https://github.com/samoshkin/tmux-config \ + && ./tmux-config/install.sh \ + && rm -rf ./tmux-config + +ENV TERM=xterm-256color + + diff --git a/tmux/tmux-config/install.sh b/tmux/tmux-config/install.sh new file mode 100755 index 0000000..1a09b66 --- /dev/null +++ b/tmux/tmux-config/install.sh @@ -0,0 +1,43 @@ +#!/usr/bin/env bash + +set -e +set -u +set -o pipefail + +is_app_installed() { + type "$1" &>/dev/null +} + +REPODIR="$(cd "$(dirname "$0")"; pwd -P)" +cd "$REPODIR"; + +if ! is_app_installed tmux; then + printf "WARNING: \"tmux\" command is not found. \ +Install it first\n" + exit 1 +fi + +if [ ! -e "$HOME/.tmux/plugins/tpm" ]; then + printf "WARNING: Cannot found TPM (Tmux Plugin Manager) \ + at default location: \$HOME/.tmux/plugins/tpm.\n" + git clone https://github.com/tmux-plugins/tpm ~/.tmux/plugins/tpm +fi + +if [ -e "$HOME/.tmux.conf" ]; then + printf "Found existing .tmux.conf in your \$HOME directory. Will create a backup at $HOME/.tmux.conf.bak\n" +fi + +cp -f "$HOME/.tmux.conf" "$HOME/.tmux.conf.bak" 2>/dev/null || true +cp -a ./tmux/. "$HOME"/.tmux/ +ln -sf .tmux/tmux.conf "$HOME"/.tmux.conf; + +# Install TPM plugins. +# TPM requires running tmux server, as soon as `tmux start-server` does not work +# create dump __noop session in detached mode, and kill it when plugins are installed +printf "Install TPM plugins\n" +tmux new -d -s __noop >/dev/null 2>&1 || true +tmux set-environment -g TMUX_PLUGIN_MANAGER_PATH "~/.tmux/plugins" +"$HOME"/.tmux/plugins/tpm/bin/install_plugins || true +tmux kill-session -t __noop >/dev/null 2>&1 || true + +printf "OK: Completed\n" diff --git a/tmux/tmux-config/readme.md b/tmux/tmux-config/readme.md new file mode 100644 index 0000000..3e6efac --- /dev/null +++ b/tmux/tmux-config/readme.md @@ -0,0 +1,6 @@ +Tmux configuration, that supercharges your [tmux](https://tmux.github.io/) and builds cozy and cool terminal environment. + +``` +$ ./tmux-config/install.sh +``` + diff --git a/tmux/tmux-config/tmux/renew_env.sh b/tmux/tmux-config/tmux/renew_env.sh new file mode 100755 index 0000000..31a4b7c --- /dev/null +++ b/tmux/tmux-config/tmux/renew_env.sh @@ -0,0 +1,12 @@ +#!/usr/bin/env bash + +set -eu + +pane_fmt="#{pane_id} #{pane_in_mode} #{pane_input_off} #{pane_dead} #{pane_current_command}" +tmux list-panes -s -F "$pane_fmt" | awk ' + $2 == 0 && $3 == 0 && $4 == 0 && $5 ~ /(bash|zsh|ksh|fish)/ { print $1 } +' | while read -r pane_id; do + # renew environment variables according to update-environment tmux option + # also clear screen + tmux send-keys -t "$pane_id" 'Enter' 'eval "$(tmux show-env -s)"' 'Enter' 'C-l' +done; \ No newline at end of file diff --git a/tmux/tmux-config/tmux/tmux.conf b/tmux/tmux-config/tmux/tmux.conf new file mode 100644 index 0000000..afa72fa --- /dev/null +++ b/tmux/tmux-config/tmux/tmux.conf @@ -0,0 +1,384 @@ +# ========================== +# === General settings === +# ========================== + +set -g default-terminal "screen-256color" +set -g history-limit 20000 +set -g buffer-limit 20 +set -sg escape-time 0 +set -g display-time 1500 +set -g remain-on-exit off +set -g repeat-time 300 +setw -g allow-rename off +setw -g automatic-rename off +setw -g aggressive-resize on + +# Change prefix key to C-a, easier to type, same to "screen" +unbind C-b +set -g prefix C-a + +# Set parent terminal title to reflect current window in tmux session +set -g set-titles on +set -g set-titles-string "#I:#W" + +# Start index of window/pane with 1, because we're humans, not computers +set -g base-index 1 +setw -g pane-base-index 1 + +# Enable mouse support +set -g mouse on + + +# ========================== +# === Key bindings === +# ========================== + +# Unbind default key bindings, we're going to override +unbind "\$" # rename-session +unbind , # rename-window +unbind % # split-window -h +unbind '"' # split-window +unbind } # swap-pane -D +unbind { # swap-pane -U +unbind [ # paste-buffer +unbind ] +unbind "'" # select-window +unbind n # next-window +unbind p # previous-window +unbind l # last-window +unbind M-n # next window with alert +unbind M-p # next window with alert +unbind o # focus thru panes +unbind & # kill-window +unbind "#" # list-buffer +unbind = # choose-buffer +unbind z # zoom-pane +unbind M-Up # resize 5 rows up +unbind M-Down # resize 5 rows down +unbind M-Right # resize 5 rows right +unbind M-Left # resize 5 rows left + + +# Edit configuration and reload +bind C-e new-window -n 'tmux.conf' "sh -c '\${EDITOR:-vim} ~/.tmux.conf && tmux source ~/.tmux.conf && tmux display \"Config reloaded\"'" + +# Reload tmux configuration +bind C-r source-file ~/.tmux.conf \; display "Config reloaded" + +# new window and retain cwd +bind c new-window -c "#{pane_current_path}" + +# Prompt to rename window right after it's created +set-hook -g after-new-window 'command-prompt -I "#{window_name}" "rename-window '%%'"' + +# Rename session and window +bind r command-prompt -I "#{window_name}" "rename-window '%%'" +bind R command-prompt -I "#{session_name}" "rename-session '%%'" + +# Split panes +bind | split-window -h -c "#{pane_current_path}" +bind _ split-window -v -c "#{pane_current_path}" + +# Select pane and windows +bind -r C-[ previous-window +bind -r C-] next-window +bind -r [ select-pane -t :.- +bind -r ] select-pane -t :.+ +bind -r Tab last-window # cycle thru MRU tabs +bind -r C-o swap-pane -D + +# Zoom pane +bind + resize-pane -Z + +# Link window +bind L command-prompt -p "Link window from (session:window): " "link-window -s %% -a" + +# Swap panes back and forth with 1st pane +# When in main-(horizontal|vertical) layouts, the biggest/widest panel is always @1 +bind \ if '[ #{pane_index} -eq 1 ]' \ + 'swap-pane -s "!"' \ + 'select-pane -t:.1 ; swap-pane -d -t 1 -s "!"' + +# Kill pane/window/session shortcuts +bind x kill-pane +bind X kill-window +bind C-x confirm-before -p "kill other windows? (y/n)" "kill-window -a" +bind Q confirm-before -p "kill-session #S? (y/n)" kill-session + +# Merge session with another one (e.g. move all windows) +# If you use adhoc 1-window sessions, and you want to preserve session upon exit +# but don't want to create a lot of small unnamed 1-window sessions around +# move all windows from current session to main named one (dev, work, etc) +bind C-u command-prompt -p "Session to merge with: " \ + "run-shell 'yes | head -n #{session_windows} | xargs -I {} -n 1 tmux movew -t %%'" + +# Detach from session +bind d detach +bind D if -F '#{session_many_attached}' \ + 'confirm-before -p "Detach other clients? (y/n)" "detach -a"' \ + 'display "Session has only 1 client attached"' + +# Hide status bar on demand +bind C-s if -F '#{s/off//:status}' 'set status off' 'set status on' + + + +# ================================================== +# === Window monitoring for activity and silence === +# ================================================== +bind m setw monitor-activity \; display-message 'Monitor window activity [#{?monitor-activity,ON,OFF}]' +bind M if -F '#{monitor-silence}' \ + 'setw monitor-silence 0 ; display-message "Monitor window silence [OFF]"' \ + 'command-prompt -p "Monitor silence: interval (s)" "setw monitor-silence %%"' + +# Activity bell and whistles +set -g visual-activity on + +# TODO: Does not work as well, check on newer versions +# set -g visual-silence on + +# BUG: bell-action other ignored · Issue #1027 · tmux/tmux · GitHub - https://github.com/tmux/tmux/issues/1027 +# set -g visual-bell on +# setw -g bell-action other + +# ================================================ +# === Copy mode, scroll and clipboard === +# ================================================ +set -g @copy_use_osc52_fallback on + +# Prefer vi style key table +setw -g mode-keys vi + +bind p paste-buffer +bind C-p choose-buffer + +# trigger copy mode by +bind -n M-Up copy-mode + +# Scroll up/down by 1 line, half screen, whole screen +bind -T copy-mode-vi M-Up send-keys -X scroll-up +bind -T copy-mode-vi M-Down send-keys -X scroll-down +bind -T copy-mode-vi M-PageUp send-keys -X halfpage-up +bind -T copy-mode-vi M-PageDown send-keys -X halfpage-down +bind -T copy-mode-vi PageDown send-keys -X page-down +bind -T copy-mode-vi PageUp send-keys -X page-up + +# When scrolling with mouse wheel, reduce number of scrolled rows per tick to "2" (default is 5) +bind -T copy-mode-vi WheelUpPane select-pane \; send-keys -X -N 2 scroll-up +bind -T copy-mode-vi WheelDownPane select-pane \; send-keys -X -N 2 scroll-down + +# wrap default shell in reattach-to-user-namespace if available +# there is some hack with `exec & reattach`, credits to "https://github.com/gpakosz/.tmux" +# don't really understand how it works, but at least window are not renamed to "reattach-to-user-namespace" +if -b "command -v reattach-to-user-namespace > /dev/null 2>&1" \ + "run 'tmux set -g default-command \"exec $(tmux show -gv default-shell) 2>/dev/null & reattach-to-user-namespace -l $(tmux show -gv default-shell)\"'" + +yank="~/.tmux/yank.sh" + +# Copy selected text +bind -T copy-mode-vi Enter send-keys -X copy-pipe-and-cancel "$yank" +bind -T copy-mode-vi y send-keys -X copy-pipe-and-cancel "$yank" +bind -T copy-mode-vi Y send-keys -X copy-line \;\ + run "tmux save-buffer - | $yank" +bind-key -T copy-mode-vi D send-keys -X copy-end-of-line \;\ + run "tmux save-buffer - | $yank" +bind -T copy-mode-vi C-j send-keys -X copy-pipe-and-cancel "$yank" +bind-key -T copy-mode-vi A send-keys -X append-selection-and-cancel \;\ + run "tmux save-buffer - | $yank" + +# Copy selection on drag end event, but do not cancel copy mode and do not clear selection +# clear select on subsequence mouse click +bind -T copy-mode-vi MouseDragEnd1Pane \ + send-keys -X copy-pipe "$yank" +bind -T copy-mode-vi MouseDown1Pane select-pane \;\ + send-keys -X clear-selection + +# iTerm2 works with clipboard out of the box, set-clipboard already set to "external" +# tmux show-options -g -s set-clipboard +# set-clipboard on|external + +# ===================================== +# === Theme === +# ===================================== + +# Feel free to NOT use this variables at all (remove, rename) +# this are named colors, just for convenience +color_orange="colour166" # 208, 166 +color_purple="colour134" # 135, 134 +color_green="colour076" # 070 +color_blue="colour39" +color_yellow="colour220" +color_red="colour160" +color_black="colour232" +color_white="white" # 015 + +# This is a theme CONTRACT, you are required to define variables below +# Change values, but not remove/rename variables itself +color_dark="$color_black" +color_light="$color_white" +color_session_text="$color_blue" +color_status_text="colour245" +color_main="$color_orange" +color_secondary="$color_purple" +color_level_ok="$color_green" +color_level_warn="$color_yellow" +color_level_stress="$color_red" +color_window_off_indicator="colour088" +color_window_off_status_bg="colour238" +color_window_off_status_current_bg="colour254" + +# ===================================== +# === Appearence and status bar === +# ====================================== + +set -g mode-style "fg=default,bg=$color_main" + +# command line style +set -g message-style "fg=$color_main,bg=$color_dark" + +# status line style +set -g status-style "fg=$color_status_text,bg=$color_dark" + +# window segments in status line +set -g window-status-separator "" +separator_powerline_left="" +separator_powerline_right="" + +# setw -g window-status-style "fg=$color_status_text,bg=$color_dark" +setw -g window-status-format " #I:#W " +setw -g window-status-current-style "fg=$color_light,bold,bg=$color_main" +setw -g window-status-current-format "#[fg=$color_dark,bg=$color_main]$separator_powerline_right#[default] #I:#W# #[fg=$color_main,bg=$color_dark]$separator_powerline_right#[default]" + +# when window has monitoring notification +setw -g window-status-activity-style "fg=$color_main" + +# outline for active pane +setw -g pane-active-border-style "fg=$color_main" + +# general status bar settings +set -g status on +set -g status-interval 5 +set -g status-position top +set -g status-justify left +set -g status-right-length 100 + +# define widgets we're going to use in status bar +# note, that this is not the complete list, some of them are loaded from plugins +wg_session="#[fg=$color_session_text] #S #[default]" +wg_battery="#{battery_status_fg} #{battery_icon} #{battery_percentage}" +wg_date="#[fg=$color_secondary]%h %d %H:%M#[default]" +wg_user_host="#[fg=$color_secondary]#(whoami)#[default]@#H" +wg_is_zoomed="#[fg=$color_dark,bg=$color_secondary]#{?window_zoomed_flag,[Z],}#[default]" +# TODO: highlighted for nested local session as well +wg_is_keys_off="#[fg=$color_light,bg=$color_window_off_indicator]#([ $(tmux show-option -qv key-table) = 'off' ] && echo 'OFF')#[default]" + +set -g status-left "$wg_session" +set -g status-right "#{prefix_highlight} $wg_is_keys_off $wg_is_zoomed #{sysstat_cpu} | #{sysstat_mem} | #{sysstat_loadavg} | $wg_user_host | $wg_date $wg_battery #{online_status}" + +# online and offline icon for tmux-online-status +set -g @online_icon "#[fg=$color_level_ok]●#[default]" +set -g @offline_icon "#[fg=$color_level_stress]●#[default]" + +# Configure view templates for tmux-plugin-sysstat "MEM" and "CPU" widget +set -g @sysstat_mem_view_tmpl 'MEM:#[fg=#{mem.color}]#{mem.pused}#[default] #{mem.used}' + +# Configure colors for tmux-plugin-sysstat "MEM" and "CPU" widget +set -g @sysstat_cpu_color_low "$color_level_ok" +set -g @sysstat_cpu_color_medium "$color_level_warn" +set -g @sysstat_cpu_color_stress "$color_level_stress" + +set -g @sysstat_mem_color_low "$color_level_ok" +set -g @sysstat_mem_color_medium "$color_level_warn" +set -g @sysstat_mem_color_stress "$color_level_stress" + +set -g @sysstat_swap_color_low "$color_level_ok" +set -g @sysstat_swap_color_medium "$color_level_warn" +set -g @sysstat_swap_color_stress "$color_level_stress" + + +# Configure tmux-battery widget colors +set -g @batt_color_full_charge "#[fg=$color_level_ok]" +set -g @batt_color_high_charge "#[fg=$color_level_ok]" +set -g @batt_color_medium_charge "#[fg=$color_level_warn]" +set -g @batt_color_low_charge "#[fg=$color_level_stress]" + +# Configure tmux-prefix-highlight colors +set -g @prefix_highlight_output_prefix '[' +set -g @prefix_highlight_output_suffix ']' +set -g @prefix_highlight_fg "$color_dark" +set -g @prefix_highlight_bg "$color_secondary" +set -g @prefix_highlight_show_copy_mode 'on' +set -g @prefix_highlight_copy_mode_attr "fg=$color_dark,bg=$color_secondary" + + +# ===================================== +# === Renew environment === +# ===================================== +set -g update-environment \ + "DISPLAY\ + SSH_ASKPASS\ + SSH_AUTH_SOCK\ + SSH_AGENT_PID\ + SSH_CONNECTION\ + SSH_TTY\ + WINDOWID\ + XAUTHORITY" + +bind '$' run "~/.tmux/renew_env.sh" + + +# ============================ +# === Plugins === +# ============================ +set -g @plugin 'tmux-plugins/tpm' +set -g @plugin 'tmux-plugins/tmux-battery' +set -g @plugin 'tmux-plugins/tmux-prefix-highlight' +set -g @plugin 'tmux-plugins/tmux-online-status' +set -g @plugin 'tmux-plugins/tmux-sidebar' +set -g @plugin 'tmux-plugins/tmux-copycat' +set -g @plugin 'tmux-plugins/tmux-open' +set -g @plugin 'samoshkin/tmux-plugin-sysstat' + +# Plugin properties +set -g @sidebar-tree 't' +set -g @sidebar-tree-focus 'T' +set -g @sidebar-tree-command 'tree -C' + +set -g @open-S 'https://www.google.com/search?q=' + + +# ============================================== +# === Nesting local and remote sessions === +# ============================================== + +# Session is considered to be remote when we ssh into host +if-shell 'test -n "$SSH_CLIENT"' \ + 'source-file ~/.tmux/tmux.remote.conf' + +# We want to have single prefix key "C-a", usable both for local and remote session +# we don't want to "C-a" + "a" approach either +# Idea is to turn off all key bindings and prefix handling on local session, +# so that all keystrokes are passed to inner/remote session + +# see: toggle on/off all keybindings · Issue #237 · tmux/tmux - https://github.com/tmux/tmux/issues/237 + +# Also, change some visual styles when window keys are off +bind -T root F12 \ + set prefix None \;\ + set key-table off \;\ + set status-style "fg=$color_status_text,bg=$color_window_off_status_bg" \;\ + set window-status-current-format "#[fg=$color_window_off_status_bg,bg=$color_window_off_status_current_bg]$separator_powerline_right#[default] #I:#W# #[fg=$color_window_off_status_current_bg,bg=$color_window_off_status_bg]$separator_powerline_right#[default]" \;\ + set window-status-current-style "fg=$color_dark,bold,bg=$color_window_off_status_current_bg" \;\ + if -F '#{pane_in_mode}' 'send-keys -X cancel' \;\ + refresh-client -S \;\ + +bind -T off F12 \ + set -u prefix \;\ + set -u key-table \;\ + set -u status-style \;\ + set -u window-status-current-style \;\ + set -u window-status-current-format \;\ + refresh-client -S + +# Run all plugins' scripts +run '~/.tmux/plugins/tpm/tpm' diff --git a/tmux/tmux-config/tmux/tmux.remote.conf b/tmux/tmux-config/tmux/tmux.remote.conf new file mode 100644 index 0000000..ea385bc --- /dev/null +++ b/tmux/tmux-config/tmux/tmux.remote.conf @@ -0,0 +1,10 @@ +# show status bar at bottom for remote session, +# so it do not stack together with local session's one +set -g status-position bottom + +# Set port of SSH remote tunnel, where tmux will pipe buffers to transfer on local machine for copy +set -g @copy_backend_remote_tunnel_port 11988 + +# In remote mode we don't show "clock" and "battery status" widgets +set -g status-left "$wg_session" +set -g status-right "#{prefix_highlight} $wg_is_keys_off $wg_is_zoomed #{sysstat_cpu} | #{sysstat_mem} | #{sysstat_loadavg} | $wg_user_host | #{online_status}" diff --git a/tmux/tmux-config/tmux/yank.sh b/tmux/tmux-config/tmux/yank.sh new file mode 100755 index 0000000..83686c6 --- /dev/null +++ b/tmux/tmux-config/tmux/yank.sh @@ -0,0 +1,68 @@ +#!/usr/bin/env bash + +set -eu + +is_app_installed() { + type "$1" &>/dev/null +} + +# get data either form stdin or from file +buf=$(cat "$@") + +copy_backend_remote_tunnel_port=$(tmux show-option -gvq "@copy_backend_remote_tunnel_port") +copy_use_osc52_fallback=$(tmux show-option -gvq "@copy_use_osc52_fallback") + +# Resolve copy backend: pbcopy (OSX), reattach-to-user-namespace (OSX), xclip/xsel (Linux) +copy_backend="" +if is_app_installed pbcopy; then + copy_backend="pbcopy" +elif is_app_installed reattach-to-user-namespace; then + copy_backend="reattach-to-user-namespace pbcopy" +elif [ -n "${DISPLAY-}" ] && is_app_installed xsel; then + copy_backend="xsel -i --clipboard" +elif [ -n "${DISPLAY-}" ] && is_app_installed xclip; then + copy_backend="xclip -i -f -selection primary | xclip -i -selection clipboard" +elif [ -n "${copy_backend_remote_tunnel_port-}" ] \ + && (netstat -f inet -nl 2>/dev/null || netstat -4 -nl 2>/dev/null) \ + | grep -q "[.:]$copy_backend_remote_tunnel_port"; then + copy_backend="nc localhost $copy_backend_remote_tunnel_port" +fi + +# if copy backend is resolved, copy and exit +if [ -n "$copy_backend" ]; then + printf "%s" "$buf" | eval "$copy_backend" + exit; +fi + + +# If no copy backends were eligible, decide to fallback to OSC 52 escape sequences +# Note, most terminals do not handle OSC +if [ "$copy_use_osc52_fallback" == "off" ]; then + exit; +fi + +# Copy via OSC 52 ANSI escape sequence to controlling terminal +buflen=$( printf %s "$buf" | wc -c ) + +# https://sunaku.github.io/tmux-yank-osc52.html +# The maximum length of an OSC 52 escape sequence is 100_000 bytes, of which +# 7 bytes are occupied by a "\033]52;c;" header, 1 byte by a "\a" footer, and +# 99_992 bytes by the base64-encoded result of 74_994 bytes of copyable text +maxlen=74994 + +# warn if exceeds maxlen +if [ "$buflen" -gt "$maxlen" ]; then + printf "input is %d bytes too long" "$(( buflen - maxlen ))" >&2 +fi + +# build up OSC 52 ANSI escape sequence +esc="\033]52;c;$( printf %s "$buf" | head -c $maxlen | base64 | tr -d '\r\n' )\a" +esc="\033Ptmux;\033$esc\033\\" + +# resolve target terminal to send escape sequence +# if we are on remote machine, send directly to SSH_TTY to transport escape sequence +# to terminal on local machine, so data lands in clipboard on our local machine +pane_active_tty=$(tmux list-panes -F "#{pane_active} #{pane_tty}" | awk '$1=="1" { print $2 }') +target_tty="${SSH_TTY:-$pane_active_tty}" + +printf "$esc" > "$target_tty"